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.
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.0This 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!
Influenceassert{ 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.
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 }!

assert_equal(x, 42) --> expected but was \n
assert_not_equal(x, 43) --> expected to be != to \n
Dunno if it was intentional or not, but you made the classical mistake of having the arguments backwards in the assertions. You don't expect 42 to be x, you expect x to be 42.
The fact that assert_equal and friends have the arguments in so counterintuitive order was one of the reasons that drove me to BDD and RSpec. And I couldn't be happier :-)
@Jarkko: no offense, but I have serious doubts about the wisdom of RSpec. Others just think it's a terrible idea.
You're spot on about the arguments being reversed, though :)
@Ovid, I'm not sure that RSpec is a terrible idea as such, but its implementation (at least at the syntax level) has some bogosity. I still think that the Perl test style has the greatest advantages over the default xUnit style.
you made the classical mistake of having the arguments backwards in the assertions
Two mistakes - one mine and one belonging to assert_equal.
All my classic assert_equalses, in every project I ever wrote, go assert_equal(reference, sample). The above is the only time I ever wrote (sample, reference). I did it as a lexical technique, to obey "use parallel construction on parallel concepts".
The other mistake is assert_equal's own verbiage. If you must invent a new assert_equal (if, for example, your library does not support the reflection that assert{ 2.0 } requires), then you should make your verbiage as neutral as possible. " should equal ". That way the argument order should not matter - even if all your test cases use a standard order!
the Perl test style
Oh, guys, please feel free to have an unrelated language shoot-out, here, so my blog entry will go to the top of "The Hot 25"! Thanks!
@Ovid: The link you reference is full of incorrect assumptions about RSpec. Source filtering? Perhaps you'd have to do that in Perl to achieve that kind of syntax, but Rspec is valid Ruby. And you don't have to define "should" and "should_not" methods. RSpec adds them automatically to every Object, something you cannot do in Perl without a lot of AUTOLOAD hackery. RSpec is more elegant than he gives it credit for, because it takes advantage of these Rubyisms.
This looks very cool! I'm a bdd guy myself but will take a look at using this w/ my test/spec helpers, as it looks so much more developer friendly. Nice work.
@Mark Thomas: you wrote RSpec adds [methods] automatically to every Object, something you cannot do in Perl without a lot of AUTOLOAD hackery.
I'm not sure where you get this idea. It's trivial in Perl to add new methods to classes on the fly (no AUTOLOAD required).
*Some::Class::new_method = sub { ... };
My objection is not about source filtering. It's about adding new methods to these classes. While it's a cool idea, I don't think it's necessary or warranted. When I read tests, I want to see the behavior of the code, but adding new behavior to classes solely for the purposes of test suites seems a bit dodgy.
That being said, maybe it will turn out to be a non-issue. There's a big difference between me thinking about how code should be and actually trying it :)
@Ovid:
Your example only adds a method to *one* hardcoded class. RSpec adds a method to Object, which is inherited by *all* classes.
but adding new behavior to classes solely for the purposes of test suites seems a bit dodgy
Hee hee... Ruby's open classes make it easy to do and it is used to great effect in many places (including Rails)
@Mark Thomas wrote: "Your example only adds a method to *one* hardcoded class."
sub UNIVERSAL::new_method { ... }
That sort of thing is frowned upon in Perl circles, but that doesn't mean it's not trivial to do.
Why even bother with deny{ x == 43 } ? Why not just assert{ x != 43 } ?
deny{} is for situations like assert_nil().
In terms of style, all programming statements should be positive. Sometimes assert{ x != 42 } is clear and expressive, and sometimes it isn't. assert{ object.nil? } might not be. deny{ object } might be.
(The source code to deny{} quote the Black Knight from "Monty Python and the Holy Grail" - "None shall pass!")
RSpec
assert2 0.2.0 might work with RSpec, like this:
assert do
my_object.my_method.should eql(42)
end
(Or whatever the syntax is. Yes I have worked with RSpec before...)
The .should should raise an exception, and assert will decorate it with the assertion reflections, and raise it again.
You can customize the messages for the old assertions:
assert_not_nil(topics[topic], "topics[topic] expected not to be nil")
Of course, that's not DRY. To me thought, that's just a great hint that we could do a lot better fine tuning the message, which shouldn't really be code centric anyway:
assert_not_nil(topics[topic], "Your topic (#{topic}) was not found in the list of topics")
I may be missing something, but I'm not sure that assert { 2.0 } offers much different over Test::Unit's assert_block().
This method is what's used to implement all of Test::Unit's custom assertions.
assert_block does not reflect its variables' values when it flunks.
And few custom assertions use assert_block. That's just wishful thinking in assert_block's documentation. (There are also plenty of reasons not to use it, including it will waste time formatting an error message, each time the assertion passes.)
Honestly, even if assert_block has the same functionality as assert 2.0, or even if a thousand other hacks and work arounds achieved the same overall result, the main idea here is that assert 2.0 makes this stuff *easy*...why hack when you can do things cleanly?
You can customize the messages for the old assertions
You can, but again, its down to convenience...why should I waste time trying to invent a meaningful error message when the most meaningful thing I could get back would be the expression I wrote and a clear reason why it failed?
One theme of misunderstanding here. assert{ 2.0 } is not...
- a DSL like RSpec
- a replacement for "custom assertions"
It lets you write whatever DSL works for you inside the {}. And it should not compete with domain-specific assertions, or application-specific assertion. They should report specific details when they fail, not complex generalities.
assert{ 2.0 } replaces all the primitive RubyUnit assertions except the block-oriented ones, like assert_raise, and the domain-specific ones, like assert_in_delta. That is specific to the domain of floating point numbers.
assert{ 2.0 } looks pretty similar to doctesting, except with doctesting you can just grab an interactive session and paste it into a file and it becomes a test suite.
>> topics = create_topics
>> topics['first']
=> 'a topic'
>> topics['second']
=> nil
>> topics['third'].index('substring')
5
In the interests of keeping the language/DSL war going *groan*...
@Ovid: I've been using RSpec on numerous projects for quite a while now and while there are times when it doesn't behave exactly as expected, and delving into the internals to see what has really happened can be troublesome, I thought I'd pick up this point:
"When I read tests, I want to see the behavior of the code, but adding new behavior to classes solely for the purposes of test suites seems a bit dodgy."
When I read tests, I want to see the intention of the code. RSpec lets me sit down with the client, detail the business requirements in language they understand, and then implement the tests in the same language. I can then setup continuous integration to generate a specdoc report so that they can monitor progress on demand and see what core bits of functionality are working, and what I know isn't. It keeps them in the feedback loop without disturbing me, and gives them a point of reference so they can differentiate between "this isn't working" and "this isn't working as I expected".
The only time I want to see the behaviour of the code in this scenario is when things aren't working, and if the RSpec DSL doesn't give me the language or output I desire then it's trivial to write my own custom matcher.
As for the merits of assert{ 2.0 } it obviously depends on your desire. It doesn't suit me as it's not code that could be read and understood by someone that doesn't understand ruby and removes legibility in the interests of improving debugging when there is an error. I use tests as much for documentation as I do error trapping.
You can customize the messages for the old assertions:
assert_not_nil(topics[topic], "topics[topic] expected not to be nil")
That's not very DRY. It repeats...
"assert" and "expected"
not nil
"topics[topic]"
To improve that diagnostic message, you'd also have to reflect the contents of topics, and the value found at topics[topic]. And that wouldn't be DRY, either.
After improving that diagnostic, you must find every other primitive assert_*() in your program. You must upgrade each and every one of their diagnostic messages, to achieve that level of detail. You must do all of them, because you don't know which one will bite you at code maintenance time.
And all of those diagnostic messages would not be DRY. That means you get all the common problems with duplicated code. The code might change, leaving the diagnostic messages behind. Then, when they fail, instead of saying "that should not be nil!", they might say something misleading.
Diagnostic messages are like comments - they can lie more easily than code can!
The only time I want to see the behaviour of the code in this scenario is when things aren't working, and if the RSpec DSL doesn't give me the language or output I desire then it's trivial to write my own custom matcher.
In the TestFoodPyramid , the peak is QA tests, soak tests, integration tests, permutation tests, etc.
Down in the middle are the customer-facing tests, acceptance tests, functional tests, etc. You are describing that layer. No assertion should be primitive!
At the bottom layer, every line of code should have matching, primitive tests. These must be dirt-simple to write. assert_equal was invented to provide these conveniences. Your comments also apply to assert_equal. Of _course_ you should productize and self-document the higher-level tests.
You can't DSL the bottom layer of that pyramid, very simply because unit test code should run as close as possible to the tested code. Any layer of abstraction adds noise to the signal from the raw tests...
The problem with RSpec, in my rose-colored syntax world, is it's the fact it's a huge collection of DSL anti-patterns. At any given moment you wonder, "Do I use a period, and underscore, or a space to separate these words?" I'm all for "fluent" syntax, but I think there's a line it crosses (just like AppleScript does), and it's sad, given the smart people involved in the project and some other very cool things I think RSpec is doing right.
Another gripe: like being able to scan the left side of a chunk of tests and see what's expected all in one place; RSpec makes me read across every line (since it feels the inexplicable need to be a sentence), which just slows me down.
I wish the community would have jumped on a lighter weight BDD framework; thankfully it's losing some weight (mocking) and has lost some of it's NIH syndrome oddities (ie, inexplicably writing a new runner vs sitting on top of Test::Unit). I won't hold my breath on the syntax (whereas spacing out an 'assert' and the curly brace is easy to do for this library ;-)
Will be nice to see if this works with test/spec.
The problem with RSpec is it has very little to do with my assertion, I did not target it, I was not even thinking about it, and it is welcome to its niche. I also did not mention it in my original post, but that hasn't stopped everyone from replying as if I did!
That said, I just now spent this evening upgrading a Beast test case into a spec ... case. Thing. Here's the result:
it 'should require body for post' do
post.valid?
post.errors.on(:body).should match(/can't be blank/)
end
We may eventually install the RubyReflector inside RSpec, so it actually help provide all those "well-formed English sentences" at fault time. However, RSpec's usefulness is when it passes, and emits a client-readable list of everything they are getting. Errors are developer-facing.
To demonstrate this, I put assert{} inside it{}, inserted a fault, and pulled the rip-cord:
it 'should require body for post' do
assert do
@post.valid?
@post.errors.on(:body).should match(/can't be blonk/)
end
end
I got an unholy blast of stack traces, object inspections, and (yes) reflected source with its values. Here's an excerpt:
Test::Unit::AssertionFailedError in 'Post should require body for post'
#
/home/phlip/projects/beast/stable-1.0/vendor/plugins/rspec/lib/spec/expectations.rb:52:in `fail_with'
...
/home/phlip/projects/beast/stable-1.0/vendor/plugins/rspec/lib/spec/expectations/handler.rb:21:in `handle_matcher'
/home/phlip/projects/beast/stable-1.0/vendor/plugins/rspec/lib/spec/runner/command_line.rb:19:in `run'
/home/phlip/projects/beast/stable-1.0/vendor/plugins/rspec/bin/spec:4
assert{ @post.valid?()
@post.errors().on(:body).should(match(/can't be blonk/))
} --> nil - should pass
@post --> # false
@post.errors() --> #["can't be blank"], "user_id"=>["can't b***
@post.errors().on(:body) --> "can't be blank"
match(/can't be blonk/) --> #
@post.errors().on(:body).should(match(/can't be blonk/))
m--? expected "can't be blank" to match /can't be blonk/.
./spec/models/post_spec.rb:12:
That's obviously pure heaven for a Real Programmer, but don't show that to a civilian client unless you are very good at CPR!
Ever since inventing assert{ 2.0 }, my career path has not lead me to write any new modules for anything with it! When that happens, I will be better prepared to demonstrate how to write DRY code inside its block that reflects error messages cleanly. And my RubyReflector is also available for other assertion systems to use, too...
very nice code construction
Consider this assertion:
assert complex_thing
Under any classical assertion system, this upgrade would be unwise:
assert complex_thing and another_complex_thing
You must put the other complex thing into its own disjoint assertion.
The assert{ 2.0 } fix:
assert{ complex_thing and another_complex_thing }
That's less risky. There's other reasons not to make assertions too complex, but "bad diagnosis at fault time" is no longer one of them. If the assertion fails, the diagnostic will state which branch of the and failed.