Behavior Driven Development Using Ruby (Part 1)by Gregory Brown
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:
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.