Here is a bit of work on Behavior Driven Development (BDD) with rspec in Ruby, based off the examples provided by this excellent Null Is Love series of posts. The only thing I know externally, in terms of BDD, is the process itself:
- Write a test for the functionality you want.
- Watch the test fail.
- Write code to support that functionality.
- Watch the test pass, or watch it fail, and fix your code.
- Repeat incrementally.
This post assumes that you’ve read and gone through the series of posts on Null Is Love. Gain an understanding of unit testing with those posts, and then come back here.
With that in mind, let’s start.
$ sudo gem install rspec -y
$ mkdir car
$ touch car/car_spec.rb car/car.rb
This is the gem we need, rspec, followed by the files we will be working with. So let’s start with the first leg of this - writing the functionality we want. Initially we will just want our Car object to initialize with the proper default values. We won’t be using all of the specification funcationality of rspec—just using it to write cleaner, english-language tests is enough for now.
car/car_spec.rb
require 'car' describe Car do it "should initialize with the proper defaults" do default_car = Car.new default_car.model.should == 'Volvo' default_car.year.should == 2007 default_car.color.should == 'unknown' end end
The first thing you may notice is that this code is very clean, and extremely readable. It also does not try to over-extend itself. We are testing one small portion of our eventual code’s functionality.
Now we test our code.
$ spec -c car_spec.rb
./car_spec.rb:3: uninitialized constant Car (NameError)
Whoops! That’s right, we haven’t written any code yet. Our test will fail. This is what we want. If the tests passed immediately, we wouldn’t need to write them at all. This is a rather roundabout way of thinking about what we are doing, but it works. (NOTE: I am using the ‘-c’ parameter with spec in order to see colored output. This may or may not work on whatever terminal or terminal emulator you are using, but it is a wonderful way to feel better about your code when you start the green that lets you know your tests have passed.)
Let us start writing our code, then.
car/car.rb
class Car attr_accessor :model, :year, :color def initialize( options = {} ) self.model = options[:model] || 'Volvo' self.year = options[:year] || 2007 self.color = options[:color] || 'unknown' end end
And now:
$ spec -c car_spec.rb
.
Finished in 0.016665 seconds
1 example, 0 failures
Beautiful! We’re green.
Here are some other tests that reflect the concerns of the original design. Each of them requires no changes to the object code in order to pass.
it "should use values provided as parameters to the constructor" do custom_car = Car.new :model => 'Toyota', :year => 2005, :color => 'white' custom_car.model.should == 'Toyota' custom_car.year.should == 2005 custom_car.color.should == 'white' end it "should accept constructor parameters in any order" do shuffled_car = Car.new :color => 'red', :model => 'Kia', :year => 2008 shuffled_car.model.should == 'Kia' shuffled_car.year.should == 2008 shuffled_car.color.should == 'red' end
Remember the part of Null is Love’s tutorial where we asked ourselves what the behavior of a certain accessor—namely, the year—should be?
Do you change your test or change your code? If you need to rely on string_car.year always being an integer, then I would suggest changing your code. If you want string_car.year to be able to accept either a string or an integer, then you change your test assertion so that “a string in results in a string out”. In most cases, it will probably the former.
With BDD, that question is asked, but in a different way. As we spec out our code, we specify what the behavior should be before we write the code that deals with it. In this case, the code will be changed to reflect the behavior we want, but that requirement is a sort of first class citizen, instead of an afterthought of going through the code, checking for gotchas.
car/car_spec.rb
# ... it "should ensure years are always integers" do old_car = Car.new :year => '1960' old_car.year.should == 1960 end #...
And the results of our test?
$ spec -c car_spec.rb
...F
1)
'Car should ensure years are always integers' FAILED
expected: 1960,
got: "1960" (using ==)
./car_spec.rb:28:
Finished in 0.017983 seconds
4 examples, 1 failure
Perfect! Now we write the code that lets our test pass—namely, converting the options[:year] parameter to an integer when it is passed.
I’ll continue this tutorial eventually, along with some additional information about heckling our code.