Behavior Driven Development (BDD) is a hot buzzword in Ruby land these days. The trouble is, it seems that not everyone knows what it means. Those that only have a basic understanding of the concepts wonder what all the fuss is about and whether it is really any different than Test Driven Development. These questions sometimes create a roadblock for those who are interested in learning about BDD without all the hype.
Of course, the best way to explore any technical concept or toolset is to simply jump in and start poking around, and see what you can figure out as you go along. That's exactly what this three part series on Behavior Driven Development using Ruby will do.
We'll start simple and work our way all the way to the BDD deep end. I've always been one to think that too much theory up front can be dangerous, so the first part of this article will immediately jump into working with the primary BDD tool Rubyists have available to them, RSpec. I'll cover some of the philosophy and core BDD concepts along the way, but my real intention is to expose you to a tool that you'll hopefully find useful at a functional level.
Once you have the basic skills down, we'll dive into more serious examples. We'll start working with mock objects and show how you can use them to focus on the behavior of a dependent system rather than its actual state. We'll talk about how to structure your specs so that they avoid the common anti-patterns present in TDD, and we'll work on more complex problems. Along the way, we'll build up a fun little application, writing the specs first, in small iterations, to help you get the feel for how BDD works.
The third part of this article will provide pointers on where to go once you've mastered RSpec and gained an appreciation for BDD. I'll show you how you can bring some of your BDD concepts back to your unit tests in places where you simply have invested too much effort to re-write everything from scratch in RSpec. We'll also cover some helpful tools to help beat up your specifications and make sure they're testing what you think they are.
My hopes are that by working through the examples you see here, you'll be able to get BDD and see how it can improve your coding experience and get you past common hurdles that folks face who are struggling a bit with TDD. Though we have a lot of ground to cover, it will be worth it in the end.
We're going to work on putting together a set of "learning specs." A couple of years ago, Mike Clark talked about how he had put together a set of unit tests while he was learning Ruby. Following his example, we'll walk through a series of specs written against Ruby's String class.
This turns out to be an ideal oppurtunity to familiarize ourselves with the RSpec framework, as we won't need to write any implementation code to see our specs run. We'll also gain a lot of clarity by writing specs instead of Test::Unit code, which might make this a very practical exploration for those new to Ruby.
I'd like to start with the very first example I wrote for this article, which was an accidental failure. See if you can catch the failure in the spec below without running it:
describe "String indexing" do it "should return first character for position 0" do "foo".should == "f" end end
It just slipped my mind why this would fail, but if you're new to Ruby, you might find the following results surprising:
$ spec string_spec.rb F 1) 'String indexing should return first character for position 0' FAILED expected "f", got 102 (using ==) ./string_spec.rb:4: Finished in 0.00995 seconds 1 example, 1 failure
What is really happening is that indexing a single character in Ruby 1.8 does not give you a single character string, instead it gives you the ASCII value of the character as an integer. We'll need to change our specifications to reflect this, but first, I'll explain a bit about what you're looking at here, for those who've never seen RSpec before.
The RSpec framework provides us a way to write specifications for our code that are executable. Technically, this is possible with any unit test framework if written correctly, but RSpec invites the programmer to take a different point of view.
Specs are made up of contexts that contain expectations in the form of examples. Here our context is defined as:
describe "String indexing" do # ... end
A context simply provides a way to group your example behaviors together around a given concept, making it easier to share a common setup or configuration between them. Right now, our context only has a single example, described as such:
it "should return first character for position 0" do "foo".should "f" end
We'll now evolve this spec so that it codifies the correct behavior, and also makes use of a common configuration which we can use to simplify the other indexing expectations that we'll add in a moment.
describe "String indexing" do before :each do @string = "foo" end it "should return first character's ASCII value for position 0" do @string.should == ?f end end
The code now checks to see that the return value is an integer matching the ASCII value of the character rather than a single character string, so the expectation is now satisfied.
In the above, you'll notice that we use the hook
before :each. This does exactly what it sounds like it does. It is called before each example is executed, allowing you to set up initial state for your specs.
We'll now add a few additional indexing examples to show how the
before :each hook is saving us some typing.
describe "String indexing" do before :each do @string = "foo" end it "should return first character's ASCII value for position 0" do @string.should == ?f end it "should return the last character's ASCII value for position -1" do @string[-1].should == ?o end it "should return the first two characters for the range 0..1" do @string[0..1].should == "fo" end it "should return the last two characters for the range -2..-1" do @string[-2..-1].should == "oo" end it "should replace the second character with a new substring" do @string = "id" @string.should == "fido" end it "should become empty when 0..-1 is set to empty string" do @string[0..-1] = "" @string.should be_empty end end
You'll notice some of these specs modify
@string. However, since it is re-initialized for each example, we don't need to worry about trashing its value.
We've also snuck in a new type of expectation, in the form of:
This is certainly pleasant to read, but far less magical than you might think. To make that clear, try changing the expectation slightly and see the error message you get:
Results in this error:
NoMethodError in 'String indexing should become empty when 0..-1 is set to empty string' undefined method 'incredible?' for "":String
This hopefully makes it immediately clear what is going on when you use the
be_something helper. RSpec is simply looking for a method called
something? on the receiving object, and validating that it returns true. This trick comes in handy often, as many Ruby objects you will work with or create on your own will have methods like this.
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.
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.
But what would we do to test both kinds of equality? This, unfortunately, would require going to the generic
assert() method in Test::Unit, but is quite elegant in RSpec:
describe "meta-test for equality expectations" do it "should easily allow testing different kinds of equality" do a = MyClass.new b = MyClass.new a.should == b a.should eql(b) end end
So again, a key reason RSpec is appealing is that it is highly transparent. We've not even gone into areas in which the BDD / TDD line is drawn in the sand, and you can already see the value of explicit specifications.
Even if you are experienced with Test::Unit, consider which approach would be easier to train a new developer on, and also, which would be more likely to encourage correct testing without tons of trial and error or documentation diving.
When it comes to using your specifications as a form of executable, self-verifying documentation, the expressiveness of RSpec pays off.
To aid in this, the framework actually provides some additional reports that can be quite helpful. For example, if you want to extract all of your example text while running the tests, you can easily do so.
Here's what it looks like when run against our String spec:
$ spec -f s string_spec.rb String indexing - should return first character's ASCII value for position 0 - should return the last character's ASCII value for position -1 - should return the first two characters for the range 0..1 - should return the last two characters for the range -2..-1 - should replace the second character with a new substring - should become empty when 0..-1 is set to empty string Finished in 0.009989 seconds 6 examples, 0 failures
You can see that, even without viewing the code, this report tells us something helpful about the behaviors we're specifying. I also find this report helpful for spot-checking my specs to make sure they're clearly testing the behaviors I'm interested in. Sometimes, a poorly named example might mean that the example itself wasn't formed as well as it could be. Although this report might not be the rosetta stone for your code, it definitely does give you a nice eagle eye view of what's going on.
You can actually pull rdoc format as well, which allows you to include this report in your online documentation:
$ spec -f r string_spec.rb # String indexing # * should return first character's ASCII value for position 0 # * should return the last character's ASCII value for position -1 # * should return the first two characters for the range 0..1 # * should return the last two characters for the range -2..-1 # * should replace the second character with a new substring # * should become empty when 0..-1 is set to empty string
What is really beautiful, however, are the HTML reports. I've added a doomed-to-fail example to show where this really shines:
$ spec -f h string_spec.rb
Though this is ultimately eye candy, it does make viewing your spec status a whole lot more pleasant. This philosophy is carried throughout RSpec, which is certainly intentional. The idea is that if it is fun or at least enjoyable to write your specs, you're more likely to write them.
Though we've only focused on one context so far, string indexing, it is easy to continue adding more as needed. Here is a sample context for string case manipulations:
describe "String case manipulations" do before :each do @string = "fOo" end it "should be converted to all caps when #upcase is sent" do @string.upcase.should == "FOO" end it "should start with a capital letter followed by lowercase letters when #capitalize is sent" do @string.capitalize.should == "Foo" end it "should be converted to all lowercase when #downcase is sent" do @string.downcase.should == "foo" end end
Notice that each context has its own
before :each method, allowing you to configure the object as needed for your examples. When we run the specs to generate the spec docs, you can see how this set of learning specifications is already painting a meaningful portrait of the behavior of Ruby strings.
$ spec -f s string_spec.rb String indexing - should return first character's ASCII value for position 0 - should return the last character's ASCII value for position -1 - should return the first two characters for the range 0..1 - should return the last two characters for the range -2..-1 - should replace the second character with a new substring - should become empty when 0..-1 is set to empty string String case manipulations - should be converted to all caps when #upcase is sent - should start with a capital letter followed by lowercase letters when #capitalize is sent - should be converted to all lowercase when #downcase is sent Finished in 0.012205 seconds 9 examples, 0 failures
From here, you're welcome to continue this example by fleshing out more of String's methods, or just wait until the next time around where we'll be tackling harder problems.
We've shown here most of what you will need to begin writing meaningful specs for your own projects, but we've not yet gone into the philosophy of BDD or talked heavily about how to use BDD as an integral part of your design process. This kind of discussion is right around the corner, and, with a working knowledge of RSpec as a tool, it will be easy to tackle the more advanced concepts as we encounter them.
If you'd like to get started with RSpec right away, you might have a look at the Test::Unit cheatsheet. This allows you to take your knowledge of Ruby's unit testing framework to learn the syntax for the various RSpec methods. We'll use everything we've discussed here and more in the articles to come, so a little practice will go a long way.
Gregory Brown is a New Haven, CT based Rubyist who spends most of his time on free software projects in Ruby. He is the original author of Ruby Reports.
Return to O'Reillynet.com.
Copyright © 2009 O'Reilly Media, Inc.