How to Put Assertions in Ruby Code

Is it idiomatic Ruby to add an assert( ) method to Ruby's Kernel class?

No it's not a best practice. The best analogy to assert() in Ruby is just raising

 raise "This is wrong" unless expr

and you can implement your own exceptions if you want to provide for more specific exception handling

How to put asserts in Selenium webdriver Ruby script?

Assertions, eg the .should==, are typically used within a test framework such as RSpec, MiniTest, Cucumber, etc. These test frameworks are designed to have reporting of the assertion's pass or fail result.

Given that you are not using a test framework, you will need to manually handle the output of results.

The most straightforward way would be to drop the assertion part and use an if statement:

element_value = browser.find_element(:xpath,"//*[@id="total"]/tbody/tr/td[4]/span").text
if element_value == "$466,634,599.67"
puts 'Test passed'
else
puts 'Test failed'
end

Note that the puts statements are used to output results to the console (unless you have changed the default output location).

Alternatively, if you do want to use the assertion, you could do:

element_value = browser.find_element(:xpath,"//*[@id="total"]/tbody/tr/td[4]/span").text
element_value.should == "$466,634,599.67"
puts 'Test passed'

In this approach, the assertion will raise an exception if the test fails. The assertion would stop the execution of the code and would therefore not output the 'Test passed' message (as expected). If the test passes, the 'Test passed' message would get outputted.

Using asserts in Ruby production... yes or no?

Asserts really shouldn't be used in production code for two reasons.

  1. assert x is very functional, and as such hard to read. Using a raise/if combo adds readability.

  2. assert doesn't make it clear what error will be raised if the condition fails. While,

    raise ObscureButInformitiveError if condition

    lets the application layers further up do something relveant. Such as emailing an admin, or writing to a perticular log.

Ruby test/unit , how do I display the messages in asserts

There isn't much choice for TAP output specifically. One test framework that did was Bacon, which was an RSpec clone with TAP output, but it doesn't appear to be active anymore.

If what you want is compatibility with Continuous Integration, The ci_reporter gem can be used to convert test results (from both Test::Unit and RSpec) to the JUnit XML format which will make it work with Jenkins/Hudson, Bamboo, and other JUnit-aware CI servers.

But unlike RSpec, Test/Unit has a limitation where it doesn't report the tests that pass. There's a discussion about running Test/Unit through the RSpec HTML formatter which will include the names of the passing tests here.

2015 update: You may want to look into MiniTest, which is gaining popularity now and has many formatter plugins and is otherwise very flexible/extendable.

Making assertions from non-test-case classes

enum_cases must be kept up to date when the production logic changes violating the DRY principle. This makes it more likely for there to be a mistake. Furthermore it is test code living in production, another red flag.

We can solve this by refactoring the case into a Hash lookup making it data driven. And also giving it a name describing what it's associated with and what it does, these are "handlers". I've also turned it into a method call making it easier to access and which will bear fruit later.

def foo_handlers
{
a: 1,
b: 2,
c: 3
}.freeze
end

def foo(input)
foo_handlers.fetch(input)
rescue KeyError
raise NotImplementedError, "Unhandled new case: #{input}"
end

Hash#fetch is used to raise a KeyError if the input is not found.

Then we can write a data driven test by looping through, not foo_handlers, but a seemingly redundant expected Hash defined in the tests.

class FooHelperTests < ActionView::TestCase
test "foo handles all expected inputs" do
expected = {
a: 1,
b: 2,
c: 3
}.freeze

# Verify expect has all the cases.
assert_equal expect.keys.sort, foo_handlers.keys.sort

# Drive the test with the expected results, not with the production data.
expected.keys do |key|
# Again, using `fetch` to get a clear KeyError rather than nil.
assert_equal foo(key), expected.fetch(value)
end
end

# Simplify the tests by separating happy path from error path.
test "foo raises NotImplementedError if the input is not handled" do
assert_raises NotImplementedError do
# Use something that obviously does not exist to future proof the test.
foo(:does_not_exist)
end
end
end

The redundancy between expected and foo_handlers is by design. You still need to change the pairs in both places, there's no way around that, but now you'll always get a failure when foo_handlers changes but the tests do not.

  • When a new key/value pair is added to foo_handlers the test will fail.
  • If a key is missing from expected the test will fail.
  • If someone accidentally wipes out foo_handlers the test will fail.
  • If the values in foo_handlers are wrong, the test will fail.
  • If the logic of foo is broken, the test will fail.

Initially you're just going to copy foo_handlers into expected. After that it becomes a regression test testing that the code still works even after refactoring. Future changes will incrementally change foo_handlers and expected.


But wait, there's more! Code which is hard to test is probably hard to use. Conversely, code which is easy to test is easy to use. With a few more tweaks we can use this data-driven approach to make production code more flexible.

If we make foo_handlers an accessor with a default that comes from a method, not a constant, now we can change how foo behaves for individual objects. This may or may not be desirable for your particular implementation, but its in your toolbox.

class Thing
attr_accessor :foo_handlers

# This can use a constant, as long as the method call is canonical.
def default_foo_handlers
{
a: 1,
b: 2,
c: 3
}.freeze
end

def initialize
@foo_handlers = default_foo_handlers
end

def foo(input)
foo_handlers.fetch(input)
rescue KeyError
raise NotImplementedError, "Unhandled new case: #{input}"
end
end

Now individual objects can define their own handlers or change the values.

thing = Thing.new
puts thing.foo(:a) # 1
puts thing.foo(:b) # 2

thing.foo_handlers = { a: 23 }
puts thing.foo(:a) # 23
puts thing.foo(:b) # NotImplementedError

And, more importantly, a subclass can change their handlers. Here we add to the handlers using Hash#merge.

class Thing::More < Thing
def default_foo_handlers
super.merge(
d: 4,
e: 5
)
end
end

thing = Thing.new
more = Thing::More.new

puts more.foo(:d) # 4
puts thing.foo(:d) # NotImplementedError

If a key requires more than a simple value, use method names and call them with Object#public_send. Those methods can then be unit tested.

def foo_handlers
{
a: :handle_a,
b: :handle_b,
c: :handle_c
}.freeze
end

def foo(input)
public_send(foo_handlers.fetch(input), input)
rescue KeyError
raise NotImplementedError, "Unhandled new case: #{input}"
end

def handle_a(input)
...
end

def handle_b(input)
...
end

def handle_c(input)
...
end

In ruby, can you execute assert_equal and other asserts while in irb?

Sure you can!

require 'test/unit'
extend Test::Unit::Assertions
assert_equal 5, 5 # <= nil
assert_equal 5, 6 # <= raises AssertionFailedError

What's going on is that all the assertions are methods in the Test::Unit::Assertions module. Extending that module from inside irb makes those methods available as class methods on main, which allows you to call them directly from your irb prompt. (Really, calling extend SomeModule in any context will put the methods in that module somewhere you can call them from the same context - main just happens to be where you are by default.)

Also, since the assertions were designed to be run from within a TestCase, the semantics might be a little different than expected: instead of returning true or false, it returns nil or raises an error.



Related Topics



Leave a reply



Submit