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 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 = "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 "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 (
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.