I like developer tests, but I don’t like the primitive assertions - assert_equal, assert_match, assert_not_nil, etc. They only exist for one reason - to print out their input values when they fail. And they don’t even reflect their variable names.

So I wrote an assertion to replace all of them. Put whatever you want into it; it prints out your expression, and all its values. Essentially like this:

__source__ __failure_diagnostic__
x = 43
assert{ x == 42 }
assert{ x == 42 } --> false - should pass
    x --> 43
deny{ x == 43 }
deny{ x == 43 } --> true - should not pass
    x --> 43

The classic versions require a lot more typing, and reflect much less information:

  assert_equal(x, 42)     --> <43> expected but was \n<42> 
  assert_not_equal(x, 43) --> <43> expected to be != to \n<43>

Install this system with:

  gem install assert2

Some systems might require sudo, to tell the ‘puter who’s boss. The “assert2” gem will pull in RubyNode, the library that inspects Ruby blocks. Then add require 'assert2' to your test suites, or to your test_helper.rb file.

Casus Belli

A student on a forum recently asked why anyone should ever use any assertion besides assert_equal. The answer relates to why we use assert_equal instead of just assert. When the classic statement assert x == 42 fails, it can only print out “false!”. The assert() method cannot see the name of x, its value, the ==, or the 42. So assert_equal is a feeble compromise. It attempts to reconstruct an expression from two input arguments.

This is a problem in all of unit testing - the cobbler’s own children always get the worst shoes! Our platform knows everything we know about assert x == 42, but it can’t tell us everything for one reason: Languages optimize for the needs of production code, not test code. So our customers will always get better tools than we get!

Ruby supplies just enough reflection for an assertion to reconstruct a block of code.

Version 2.0

This new assertion simplifies the heck out of developer tests. Before:

  def test_attributes 
    topics = create_topics 
    assert_equal 'a topic', topics['first'] 
    assert_not_nil topics['second'] 
    assert_match 'substring', topics['third'] 
  end

After:

  def test_attributes 
    topics = create_topics 
    assert{ 'a topic' == topics['first'] } 
    assert{ topics['second'] }
    assert{ topics['third'].index('substring') }
  end 

If the first assert_equal failed, it would only print out the two values. When assert{ 2.0 } fails, it prints its complete expression, with each intermediate term and its value:

  assert{ "a topic" == ( topics["first"] ) } --> false - should pass
      topics      --> {"first"=>"wrong topic"}
  topics["first"] --> "wrong topic"

And if the assert_not_nil failed, it would only reward us with the infamous diagnostic "<nil> expected to not be nil".

Traditional assertions don’t work very well with elaborate, multiple arguments. assert{ 2.0 } works best using elaborate arguments, with lots of variables, because it will print out each of their intermediate values.

This research makes lowly test suites more competitive with a professional debugger. A failure diagnostic is more useful than a breakpoint and a list of watched variables!

Influence

assert{ 2.0 } improves Test-Driven Development. Instead of carefully picking an assertion, just write whatever you like, and inspect the diagnostic. If your production code is failing for the correct reason, the diagnostic should clearly reflect this. If it does not, upgrade the source in the assertion. This process reviews the test diagnostic, to improve the odds it assists maintenance. That, in turn, encourages helpful variable names and suggestive sample data values.

When a test fails unexpectedly, that diagostic will help decrease the research required to determine whether to debug the test failure, or revert the code.

To help more, assert{ 2.0 } and deny{ 2.0 } both take diagnostic message strings. Use assert("my colleague made me do this"){...}, to ensure test failures route to the correct department!

When assert{ 2.0 } fails, it calls .inspect on each variable in its block. To help diagnose your application objects, such as your Models, override their .inspect methods, and put descriptive strings in there. My ActiveRecord projects inspect like this:

module ActiveRecord
  class Base
    alias :old_inspect :inspect

    def inspect
      return "#{self.class.name}"                unless self.respond_to? :id
      return "#{self.class.name}: #{id} '#{title}'"  if self.respond_to? :title
      return "#{self.class.name}: #{id} '#{name}'"   if self.respond_to? :name
      return "#{self.class.name}: #{id}"
    end
  end
end
Fine Print

The assertion was developed under Ruby 1.8.6…

When an assertion passes, Ruby only evaluates it once. However, when an assertion fails, the module RubyNodeReflector will re-evaluate each element in your block. (You knew there was a “gotcha”, right?;) This effect will hammer your side-effects, and will disable boolean short-circuiting. So once again sloppy developer tests help inspire us to write clean and decoupled code!

Don’t go enthusiastically replacing all your classic assertions with assert{ 2.0 }. Use it only on fresh code, so you can review how it works with your expressions.

Conclusion

The following RubyUnit assertions are now obsolete:

  • assert
  • assert_block
  • assert_equal
  • assert_instance_of
  • assert_kind_of
  • assert_operator
  • assert_match
  • assert_nil
  • assert_no_match
  • assert_not_equal
  • assert_not_nil

The next time you think to type any of them, use assert{ 2.0 } instead, and put whatever you need inside the { block }!