assert{ 2.0 } now supports Ruby 1.9.
assert{ 2.0 } is the industry's most aggressive TDD
system—for Ruby, or any other language. Each time it fails, it analyzes the
reason and presents a complete, formatted report. This makes the cause very
easy to rapidly identify and fix. assert{ 2.0 } is like a
debugger's "inspect variable" system, and it makes your TDD cycle more
effective.
Here's an example of the output diagnostic when assert{ 2.0 }
fails. The first line reflects the source of the entire assertion:
assert{ z =~ /=begon/ } # complete with comments
--> nil
z --> "<span>=begin</span>"
z =~ /=begon/ --> nil.
The second line, --> nil, is the return value of the asserted expression.
The next lines contain the complete source and re-evaluation of each
terminal (z) and expression (z =~ /=begon/) in the assertion's block.
The diagnostic lines are formatted to scan easily, and they use "pretty_inspect()"
to wrap complex outputs. The diagostic contains the name and value of every variable and
expression in the asserted block.
These simple techniques free your assertions from restricting patterns, such
as assert_equal() or
assert_match() (or their syntax-sugary equivalents!).
The more creative your assertions, the more elaborate their diagnostics.
Installation
gem install assert2 # for Ruby 1.8, via RubyNode gem install assert21 # for Ruby 1.9, via Ripper (comes with Ruby)
Then place require 'assert2' (for either package) above your tests.
These gems also provide xpath{}, to
test XML and XHTML.
assert{ boolean expression } and Fault Diagnostics
This test uses a semi-private assertion, assert_flunk(),
to detect that when assert{ 2.0 } fails, it prints out a diagnostic
containing the assertion's variables and values:
def test_assert_reflects_your_expression_in_its_fault_diagnostic x = 42 assert_flunk ' assert{ x == 43 } # even comments reflect! --> false x --> 42 x == 43 --> false' do assert{ x == 43 } # even comments reflect! end end
deny{ boolean expression }
This shows assert{}'s arch-nemesis,
deny{}. Use it when your programs
are too cheerful and happy, to bring them down:
def test_deny_reflects_your_expression_in_its_fault_diagnostic x = 42 assert_flunk ' deny{ x == 42 } --> true x --> 42 x == 42 --> true' do deny{ x == 42 } end denigh{ x == 43 } # an alternate spelling, for smooth columns of code... end
assert('extra spew'){ boolean... }
assert{} and deny{} take an optional first argument—a
string. At fault time, this appears in the output diagnostic, above all other spew:
def test_diagnostic_string x = 42 assert_flunk 'medium rare' do assert('medium rare'){ x == 43 } end end
add_diagnostic 'extra spew'
This test shows how to add extra diagnostic information to an assertion.
Custom test-side methods which know they are inside
assert{} and deny{} blocks
can use this to explain what's wrong with some situation.
def test_add_diagnostic assert_flunk /silly Rabbi!/ do deny do add_diagnostic 'silly Rabbi!' and true end end end
add_diagnostic{ 'block' }
Sometimes the diagnostic is more expensive than a passing assertion. To keep all your assertions fast, wrap your diagnostics in blocks. They only call when their assertions fail fail:
def test_add_diagnostic_lambda ark = '' assert_flunk /^remarkable/ do assert do add_diagnostic{ 'rem' + ark } and ark = 'arkable' false end end end
Classic assert( boolean )
assert{} will pass thru to the original assert()
from Test::Unit::TestCase. When you drop require 'assert2'
into your tests, all your legacy assert() calls will still perform
correctly:
def test_assert_classic assert_flunk /(false. is not true)|(Failed assertion)/ do assert false end end
Error Handling
assert{} interprets program errors and decorates their diagnostics:
def test_error_handling assert_flunk /ZeroDivisionError: divided by 0/ do assert{ 1 / 0 } # would you believe some math professors frown upon that?! end end
Compound Assertions
assert{} correctly interprets and diagnoses complex boolean
expressions, so you can interrogate related variables in one expression:
def test_error_handling x, y = 42, 43 assert_flunk /x\s+--> 42.*y\s+--> 43/m do # FIXME take out the \s+ assert{ x == 42 and y == 42 } end end
Warning: Put Assertions on Separate Lines
assert{} works by exploiting marginal features in Ruby's internal parser.
To reflect source, assertions must find clean beginnings and endings to statements.
Do not, for example, put two assertions in one line, because the first one will confuse the second one:
def test_put_assertions_on_separate_lines x = 42 assert_flunk /not like this/ do # but the outer assertion did not fail! assert('not like this'){ assert{ x == 43 } } end end
Warning: Assertions Repeat their Side-Effects
assert{} works by exploiting marginal features in Ruby's interpreter.
To reflect the value of captured expressions, they must be evaluated again.
When an assertion passes, it will only evaluate once, as a normal block. When assertions fail, however, their expressions will evaluate twice (or more!). Their side-effects might interfere with your diagnosis.
FIXME link out to Assemble Activate Assert pattern here
Do not, for example, do this:
def test_write_assertions_without_side_effects x = 42 assert_flunk '(x += 1) == 44 --> true' do # note the diagnostic says we were correct!! assert{ (x += 1) == 44 } end end
What about Ruby 1.8.7?
FIXME redo!
assert{ 2.0 } uses RubyNode, which works with 1.8.6 and lower.
assert{ 2.1 } uses Ripper, which is built into Ruby 1.9 and up.
Ruby 1.8.7 includes some backports from 1.9 that break the internal API that
RubyNode used. This means the assert{ 2.0 } project cannot support
a Ruby 1.8 greater than 1.8.6!
Those of you using a platform that automatically installed Ruby 1.8.7 can use Keith Lancaster's blog entry, "Installing RubyNode when using MacPorts", to recover some 1.8.6 stability!