O'Reilly    
 Published on O'Reilly (http://oreilly.com/)
 See this if you're having trouble printing code examples


Behavior Driven Development Using Ruby (Part 1)

by Gregory Brown
08/09/2007

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.

Learning the RSpec Basics

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:

string_spec.rb
  describe "String indexing" do

    it "should return first character for position 0" do
      "foo"[0].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"[0].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[0].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[0].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[1] = "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:

  @string.should be_empty

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:

  @string.should be_incredible

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.

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.

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.

Pretty Reports with Practical Value

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

figure

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.

Enough to Get You Started

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.