advertisement

Print

Behavior Driven Development Using Ruby (Part 1)
Pages: 1, 2, 3

To ensure that this test is actually working as we think it is, we can negate it. The method we call is should_not(), and unsurprisingly, this spec causes the failure which follows it:



  it "should become empty when 0..-1 is set to empty string" do
    @string[0..-1] = "" 
    @string.should_not be_empty
  end
'String indexing should become empty when 0..-1 is set to empty string' FAILED
expected empty? to return false, got true
./string_spec.rb:30:

Finished in 0.01074 seconds

6 examples, 1 failure

Comfortable that we got it right the first time around, we can revert to our original @string.should be_empty statement and see all the tests go green again.

Negating your specifications or forcing them to fail is a decent way to be sure they're hooked up properly. I usually start off any new spec with a test that intentionally fails, just to be sure that things are actually running. There is actually a method that makes this easy in RSpec, called violated. Here's an example that's doomed to fail:

  it 'should fail no matter what' do
    violated "The Interstellar Rules Of The Galaxy" 
  end

Though I've run into false positives less often in RSpec because it is more expressive and easier to spot bugs in than Test::Unit code is, I still carry this habit along with me. There is nothing worse than coding under the false assumption of having passing specifications, so a little paranoia up front can help save heartache (or at least headaches) down the line.

More than Just a Test::Unit Facelift

We can now take a step back and consider what RSpec is offering us when compared to Test::Unit, Ruby's baked-in testing framework. The best way to do that is to look at the equivalent Test::Unit code for comparison:

  class StringIndexingTest < Test::Unit::TestCase

    def setup
      @string = "foo" 
    end

    def test_should_return_first_char_ascii_value_for_pos_0
      assert_equal ?f, @string[0]
    end

    def test_should_return_last_chars_ascii_value_for_pos_neg_1
      assert_equal ?o, @string[-1]
    end

    def test_should_return_first_two_chars_for_range_0_to_1
      assert_equal "fo", @string[-2..-1]
    end

    def test_should_return_last_two_chars_for_range_neg_2_to_neg_1
      assert_equal "oo", @string[-2..-1]
    end

    def test_should_replace_second_char_with_new_substring
      @string[1] = "id" 
      assert_equal "fido", @string
    end

    def test_should_become_empty_when_zero_to_neg_1_set_to_empty_string
      @string[0..-1] = "" 
      assert @string.empty?
    end

  end

Looking at this code, you'll notice that, technically, it isn't very different. However, it is also clear that one framework is far more expressive than the other. Both have their conventions, but RSpec is clearly designed more for human readability than Test::Unit is. As a veteran TDDer, it is just as easy to read both for me. However, I must admit that there is a certain opaqueness to Test::Unit that requires you to know more about the framework to make use of it.

For example, take the most common method we use, assert_equal:

  assert_equal "fido", @string

After enough rounds of error messages that say "Expected first_value, but got second_value", you'll learn that the method is invoked in the form of:

  assert_equal expected, actual

Swapping them by accident leads to confusing errors and can be annoying. It's some degree more difficult to mess up RSpec, which takes the form:

  actual.should == expected

This follows our natural language, in which I would say something like, "The temperature should equal 75 degrees."

Beyond sounding nicer, there is actually a technical issue to be aware of here as well. Ruby has lots of different ways to compare values, with ability to express equivalence and sameness in addition to equality. All in all, there are at least 4 operations that have to do with this, all having potentially different meanings (eql?, equal?, ===)

When we say assert_equal, it might be tempting to assume that it uses the equal? method from the above, but a quick IRB session will show that isn't the case:

>> a = "foo" 
=> "foo" 
>> a.equal?("foo")
=> false
>> a.equal?(a)
=> true
>> a == "foo" 
>> => true

It turns out that equal? is actually the "sameness" comparison that checks to see that two values are the same object. Test::Unit does provide an assertion for this, aptly named assert_same, but the lack of transparency may be frustrating to a new user.

So, what does assert_equal actually use? A quick test will show us:

class MyClass
  def eql?(other)
    true
  end
end

if __FILE__ == $PROGRAM_NAME
  require "test/unit" 

  class MyTest < Test::Unit::TestCase

    def test_my_class_equality
      a = MyClass.new
      b = MyClass.new

      assert_equal a,b
    end

  end

end

This test fails, so we try defining ==

class MyClass
  def ==(other)
    true
  end
end

This turns out to pass and lets us know that assert_equal(expected, actual) truly is equivalent to actual.should == expected.

Pages: 1, 2, 3

Next Pagearrow