Validates this page at W3C

assert{ 2.0 }

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!