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 2)

by Gregory Brown
08/30/2007

In the first part of this article, we covered the basics for Ruby's premier BDD framework, RSpec. With that behind us, we can now explore the actual practice of Behavior-Driven Development.

BDD offers a unique opportunity to change your development process, allowing you to see things from a new angle. Rather than thinking of specifications as a way of verifying that certain rules are being followed by your code, we can look at writing our specs as an integral part of the design process. Trying to figure out how to design a system for testability can be a huge challenge. In this article, we'll look at how this can be made a lot less painful by taking things in tiny steps.

We're going to walk through the development of a small application that was downright fun to write. If you've ever played the pencil-and-paper game Dots and Boxes, you'll find this example familiar.

I've packaged the application in a way I hope you will find helpful. It is separated into 16 iterations, so you can see the source and specs evolve in the same way that I actually built it. I owe this method entirely to Trotter Cashion, who used it in a BDD talk at Gotham Ruby Conference, where it worked great. I'll be referencing iteration numbers throughout the article for those who want to follow along with the source at home.

Before we dive into our first set of specs, I'll give a very brief overview of the task at hand. We'll then continue our whirlwind tour, stopping along the way to expose key concepts, tackle some of the tricky parts of the code, and discuss best practices.

Dots and Boxes: A Simple Pencil-and-Paper Strategy Game

Though you might want to read up on the game for some more background, this paragraph from Wikipedia pretty much sums up the problem domain:

Starting with an empty grid of dots, players take turns, adding a single horizontal or vertical line between two unjoined adjacent dots. A player who completes the fourth side of a box earns one point and takes another turn. (The points are typically recorded by placing in the box an identifying mark of the player, such as an initial.) The game ends when no more lines can be placed. The winner of the game is the player with the most points.

For the visually minded, this diagram should make it pretty clear how the game is played.

Game diagram
How Dots and Boxes is played

A BDD friendly game

This makes for a great spec-writing example. There are a whole lot of places we can start because we've got a decent set of well-defined rules. Of course, we'll need to do a fair share of thinking about the problem, just to see how all the parts come together.

If we were working without tests, we might start by breaking down a part of the system and describing the system in pseudocode, or maybe work in irb throwing some ideas around. If we were doing TDD, but not test-first, we might put together a simple implementation of some of the rules, and then begin writing some units to verify things are working as expected.

With BDD, there are some practices you can take or leave depending on your preferences, but there are a few that you really need to stick to. Perhaps the most important practice to keep up with is writing your specs first, keeping them as simple as possible, and letting this process guide you through the design of your application.

To me, the easiest place to start seemed to be the box logic. It's fairly isolated, and more clear to define up front than the control processes that'll drive the actual game. We'll now take a walk through my early specs and see where they lead.

A Dots Box

We know a few things about a box in this game. It has four edges that can be drawn in by any player. When a player fills in the last remaining edge, he gains ownership of the box. We'll eventually need to think about how to position the box within a grid, but for now, this is enough to get started.

Wiring up our specs

In the last article, we were building a self-contained "learning spec." Because we'll be working with a lot more files this time around, we will want to structure our code a little more carefully.

Of course, it's nothing too fancy. My first iteration file structure looks like this:

lib/
 dots/
   box.rb
spec/
  box_spec.rb
  helper.rb

I usually start with an existence test to make sure that I've got everything wired up. Here's what that spec looks like:

(1) spec/box_spec.rb
require File.join(File.expand_path(File.dirname(__FILE__)),"helper")
require "#{LIB_DIR}/box" 

describe "A dots box" do

  before :each do
    @box = Dots::Box.new
  end

  it "should exist" do
    @box.should_not be_nil
    @box.should be_an_instance_of(Dots::Box)
  end
end

This trivial test simply checks to make sure that we can instantiate a box object, indicating that we've properly required all the files we need. If you're somewhat new to Ruby, these two lines might look surprising to you:

require File.join(File.expand_path(File.dirname(__FILE__)),"helper")
require "#{LIB_DIR}/box"

This is really just some clever path hackery that lets this file run standalone. The first line generates an absolute path to the spec/helper.rb file and requires it. That's where we get our LIB_DIR constant from, which if you take a look at the code, does the same trick to get an absolute path to our lib:

(1) spec/helper.rb
require "rubygems" 
require "spec" 

LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)), *%w[.. lib dots])

All this tweaking amounts to nothing more than making our specs a little easier to run in isolation, as well as expand down the line with helper functions or third-party extensions where needed.

We now set up an empty class to get started; it doesn't need anything else just yet:

(1) lib/dots/box.rb
module Dots
  class Box
  end
end

By running our specs, we get a verification that all of this stuff is actually working, in the form of a passing example:

$ spec spec/box_spec.rb 
.

Finished in 0.009651 seconds

1 example, 0 failures

Now, in the process of developing this spec, I actually started with code like this to make sure I could get a failure:

  it "should exist" do
    violated "but doesn't" 
  end

However, I'll leave this as an exercise to the paranoid. For those who are quickly bored by the boiler plate, we'll jump right into Box specs now and kick up the velocity with each iteration.

Start with what you know

If you've ever found yourself delighted getting all the answers to Jeopardy one night, only to remember later that evening it was a rerun from last week, get ready to feel that way again.

The key to starting any BDD project is to begin with the most obvious assumptions, and then build your way outward and upward from there. This goes for both your specs and your implementations.

We'll start with a trivial assumption. Boxes should return some sort of collection of edges, and there should be four of them.

Here's our trivial example, which states this expectation:

(2) spec/box_spec.rb
it "should have 4 edges" do
  @box.edges.size.should == 4
end

My implementation might surprise you:

(2) lib/dots/box.rb
module Dots
  class Box  
    def edges
      [nil]*4
    end
  end
end

Why would we write such a mindless implementation? The answer is simple. Anything more complicated would be assuming more than what we've specified. Of course, this code won't get us far, passing or not, so we add more specs.

(3) spec/box_spec.rb
  [:north, :south, :east, :west].each do |dir|
    it "should have an #{dir} edge" do
      @box.edges[dir].should_not be_nil
    end
  end

As you can see, this exposes a little more about what we're expecting. It now seems like edges should be a hash-like object keyed by compass location of the edge. We haven't defined what an edge is yet, which is why we don't expect more than that for these keys; the objects returned by the edges collection shouldn't be nil.

It's worth taking a quick moment to notice that we're actually dynamically building examples here. All four of these examples will be run regardless of whether some of them fail along the way. This comes in handy because it makes it immediately apparent where problems lie, and also results in nice output:

$ spec spec/box_spec.rb -f s

A dots box
- should exist
- should have 4 edges
- should have an north edge
- should have an south edge
- should have an east edge
- should have an west edge

Being able to dynamically generate examples lets you keep your expectations simple, keeping things well organized into individual scenarios.

Let's take a look at the implementation that makes the above examples pass:

(3) lib/dots/box.rb
module Dots
  class Box  
    def edges
      Hash[:north, true, :south, true, :east, true, :west, true]
    end
  end
end

Not surprisingly, it's a Jeopardy rerun all over again. But if you're feeling the strain of writing such code, it's only because you're looking at it after the fact. In practice, I flew through the first few iterations of this application in only a few moments, using them to form what you might jot down on scratch paper, or work through in your head otherwise. I tend to trust code more than I do my own ideas of what might work, so this workflow fits wonderfully for me. If you're new to this technique, it might take some time, but BDD really does help you to think in code.

Still, some of you might be itching to take a look at some code that actually does something. Things start to get interesting around iteration 6, so let's look at the full spec there:

(6) spec/box_spec.rb
require File.join(File.expand_path(File.dirname(__FILE__)),"helper")   
require "#{LIB_DIR}/box"   

def directions 
  [:north,:south,:east,:west].each { |dir| yield(dir) }
end

describe "A dots box" do    

  before :each do
    @box = Dots::Box.new 
  end

  it "should have 4 edges" do
    @box.edges.size.should == 4
  end

  directions do |dir|
    it "should have an #{dir} edge" do
      @box.edges[dir].should_not be_nil
    end 

    it "#{dir} edge should be :not_drawn by default" do
      @box.edges[dir].should == :not_drawn  
    end   

    it "#{dir} edge should be :drawn when draw_edge(#{dir.inspect}) is called" do
      @box.draw_edge(dir)
      @box.edges[dir].should == :drawn
    end
  end       

  it "should return nil for owner() by default" do
    @box.owner.should be_nil
  end

end  

describe "An incomplete dots box" do

  before :each do
    @box = Dots::Box.new   
  end   

  it "should return false for completed?" do
    @box.should_not be_completed
  end

  it "should not allow an owner to be set" do
    lambda { @box.owner = "Gregory" }.should raise_error(Dots::BoxIncompleteError)
  end

end  

describe "A completed dots box" do

  before :each do
    @box = Dots::Box.new
    directions { |dir| @box.draw_edge(dir) }
  end    

  it "should return true for completed?" do
    @box.should be_completed
  end 

  it "should allow an owner to be set" do
    @box.owner = "Gregory" 
    @box.owner.should == "Gregory" 
  end 

end

If my specs are doing their job, it should be pretty easy for you to catch up on what's going on. You can see we've gone a lot farther, defining a way to draw edges, assign ownership to a box, and even do some basic error handling. For all the behaviors described above, we end up with a beautifully simple implementation:

(6) lib/dots/box.rb
module Dots
  class Box

    def initialize
      @edges = Hash[ :north, :not_drawn, 
                     :south, :not_drawn, 
                     :east, :not_drawn, 
                     :west, :not_drawn ]
    end    

    attr_reader :edges
    attr_reader :owner

    def draw_edge(dir)
      @edges[dir] = :drawn
    end        

    def owner=(new_owner)
      raise BoxIncompleteError unless completed?
      @owner = new_owner
    end

    def completed?
      @edges.all? { |dir,status| status == :drawn }
    end

  end 

  class BoxIncompleteError < StandardError; end
end

Hopefully by seeing the results here, you'll gain a better understanding of why it is beneficial to work in such tiny steps. Because we neurotically don't add anything to the code that isn't covered by an example to our specs, we end up with very little code that isn't doing much, and absolutely no useless code. It is almost as if writing the specs first force us to write better production code, just by osmosis.

This is all pretty straightforward and simple code, of course, but before we move on to more challenging parts, let's take a quick look at some of the tricks in our specs.

You'll notice at the top of the file I've added a helper to iterate over directions, since a few of my examples use this feature.

def directions 
  [:north,:south,:east,:west].each { |dir| yield(dir) }
end

The way this is implemented, it gets defined at the top level, which means it's available everywhere. This is notably hacky, and there are generally better ways to add helper functions to your specs, which we'll show later in this article. However, this is entirely of the "keep it simple" mindset. For what we need, this gets the job done, and we can go back to it later if it gives us trouble.

The rest of the tests are fairly benign, though we do a couple of interesting things, such as make use of the be_something helper we discussed in the first part of this article in a practical way:

  it "should return false for completed?" do
    @box.should_not be_completed
  end

We also test to make sure an exception we're raising is working as expected. In RSpec, the following code is equivalent to an assert_raises call in Test::Unit:

  lambda { @box.owner = "Gregory" }.should raise_error(Dots::BoxIncompleteError)

We need to wrap the code we are testing in a Proc to prevent the error from rising up to the top level, but other than that, this code does pretty much exactly what it reads as.

Before we fast-forward to other parts of our system and get into some of the deeper topics, let's take a look about what we know about boxes:

$ spec spec/box_spec.rb -f s

A dots box
- should have 4 edges
- should have an north edge
- north edge should be :not_drawn by default
- north edge should be :drawn when draw_edge(:north) is called
- should have an south edge
- south edge should be :not_drawn by default
- south edge should be :drawn when draw_edge(:south) is called
- should have an east edge
- east edge should be :not_drawn by default
- east edge should be :drawn when draw_edge(:east) is called
- should have an west edge
- west edge should be :not_drawn by default
- west edge should be :drawn when draw_edge(:west) is called
- should return nil for owner() by default

An incomplete dots box
- should return false for completed?
- should not allow an owner to be set

A completed dots box
- should return true for completed?
- should allow an owner to be set

As you can see, our specs are already pretty handy as English documentation for our implementation. With a task like this that has pretty well defined rules, these come pretty close to comprehendable even by non-programmers.

In iterations 7-13, we continue to iron out some remaining rules about how Boxes should work, and then develop our Grid implementation. The Grid implementation doesn't show off any new RSpec or BDD tricks, so I'm not going to cover that code, but it's all in the source package if you're interested. Here's what specdocs look like, though:

An empty dots grid
- should have one box for each edge on left side
- should have one box for each edge on right side
- should have one box for each edge on top side
- should have one box for each edge on bottom side
- should have two boxes for all inner edges
- should allow connecting adjacent dots
- should throw an error when connecting non-adjacent dots

A drawn on dots grid
- should return an empty set when connect() does not complete a box
- should return a set with a box when connect() completes one box
- should return a set of two boxes when connect() completes two boxes

To keep the focus on fresh concepts, we'll make a big leap over to the controlling process code, our Game object. Here, we'll be able to take a look at how to use mock objects to keep code isolated from its dependencies, and focus on the interactions rather than the state exchange between objects.

Introducing Mock Objects via the Game Class

The purpose of our Game class is by nature one that depends on other resources to operate. It is meant to be the control class between a user interface and the underlying data models that represent the game. This is the class that will carry us through the "get input from user, make changes, update display" process. This may sound like it is a tricky thing to test, but we'll show how you can approach it in the same simple, iterative manner as we've been doing for our more isolated objects.

Although we've not shown the implementation or specs for our Grid code, you won't actually need it to follow along with our Game specs. In fact, although our Game class implementation is meant to contain a Grid object, none is actually created when we run our Game specs!

To throw yet another monkey wrench into the gears, we're actually not even going to bother building a UI until after our Game object is functioning properly. Instead, we'll be using mock objects to define a protocol that we expect the UI to implement, which we can freely add in later.

Like before, we'll start with the trivial tasks. We want to be able to get a list of players, and then populate a grid to begin a game on. Our initial examples look like this:

(13) spec/game_spec.rb
require File.join(File.expand_path(File.dirname(__FILE__)),"helper")

describe "A newly started game" do

  before :each do
    @game = Dots::Game.new
    @game.interface = mock("UI")
    mock_player_selection
    mock_prompt_for_grid_size
    @game.start
  end   

  it "should have an array of players" do 
    @game.players.should == ["Gregory","Joe"]
  end             

  it "should set the first player entered via the UI to the current_player" do
    @game.current_player.should == "Gregory" 
  end   

  it "should populate a grid from UI input" do
    @game.grid.should_not be_nil
  end

  def mock_player_selection
    @game.interface.should_receive(:get_players).and_return(["Gregory","Joe"])
  end

  def mock_prompt_for_grid_size               
    grid = mock("grid")  
    @game.interface.should_receive(:get_grid_size).and_return([8,10])            
    Dots::Grid.should_receive(:new).with(8,10).and_return(grid)
  end     

end

We've got two different mock objects being created here, one for our user interface, and another for our grid. You can see that we set the interface of the game object using mock("UI"), which is a blank mock object created by RSpec. The following method fills in our expectations:

  def mock_player_selection
    @game.interface.should_receive(:get_players).and_return(["Gregory","Joe"])
  end

Here we're basically creating a simple stubbed out object, where we expect that some_interface.get_players will be called, and return an array with some names in it. We're using this to test how our game will behave upon startup, and also to test some simple logic:

  it "should have an array of players" do 
    @game.players.should == ["Gregory","Joe"]
  end             

  it "should set the first player entered via the UI to the current_player" do
    @game.current_player.should == "Gregory" 
  end

Our mock objects will self-verify, and they'll also help us by responding things we're expecting so that we can form examples around them. It's worth keeping in mind that a mock isn't going to automatically return meaningful data--you need to take care that what you're asking for is really what you expect from your dependent classes.

Let's take a quick look at the implementation of Game so far:

(13) lib/dots/game.rb
module Dots
  class Game
    attr_accessor :interface   
    attr_reader :players, :grid            

    def start
      @players = interface.get_players
      @turn = 0 
      rows,cols = interface.get_grid_size 
      @grid = Dots::Grid.new(rows,cols)
    end  

    def current_player
      @players[@turn]    
    end

  end      
end

You can see where interface.get_players is being called, and also where our grid interaction happens. If you look over the examples, it should be pretty clear what's going on here. There is one small trick we do in our examples that's worth revisiting, though.

Adding mock calls to existing classes

With our interface code, it's very clear what's happening so long as you're familiar with duck typing. We're passing in an object that acts like something else, and Ruby is happy to work with it. However, if you look at Game#start, you can see that our Grid objects are explicitly instantiated.

We'd like to avoid testing these objects directly, for a number of reasons. Perhaps the most important is that we should be able to focus on just the interactions with that object, and not its implementation details. Rather than using a technique such as dependency injection or some other design workaround to get at this code and insert our mock objects, RSpec offers us a shortcut:

  def mock_prompt_for_grid_size               
    grid = mock("grid")  
    @game.interface.should_receive(:get_grid_size).and_return([8,10])            
    Dots::Grid.should_receive(:new).with(8,10).and_return(grid)
  end

By stubbing out the constructor call, we can have it return a mock object, which we can then define our expectations on. This largely decouples us from the class without any additional work, and with some extra boilerplate, could actually completely separate us from its implementation.

As we evolve the game class, the interactions get more complex. To cope with this, we encapsulate our helper methods in a module, which can then be included in our example sets as needed.

The final set of mock helpers for the Game specs look like this:

(15) spec/game_spec.rb
module GameStateMocks

  def mock_player_selection
    @game.interface.should_receive(:get_players).and_return(["Gregory","Joe"])
  end

  def mock_prompt_for_grid_size               
    grid = mock("grid")  
    @game.interface.should_receive(:get_grid_size).and_return([8,10])            
    Dots::Grid.should_receive(:new).with(8,10).and_return(grid)
  end

  def mock_initial_game_state
    @game.interface = mock("UI") 
    mock_player_selection
    mock_prompt_for_grid_size
    @game.interface.should_receive(:update_display).with(an_instance_of(Dots::Game))
  end  

  def stub_move(edge,boxes_completed=0)  
    @game.interface = mock("UI")             
    box_set = mock("box set")
    if boxes_completed.zero?
      box_set.should_receive(:empty?).and_return(true)
    else
      box_set.should_receive(:empty?).and_return(false)
      box_set.should_receive(:size).and_return(boxes_completed)
    end
    @game.interface.should_receive(:get_move).and_return(edge)       
    @game.interface.should_receive(:update_display).with(an_instance_of(Dots::Game))
    @game.grid.should_receive(:connect).with(*edge).and_return(box_set)
    @game.move  
  end

end

As you can see here, we've defined a whole bunch of behaviors, some for objects that don't exist yet, others for objects that we only want to test our interactions with. The key here is to mock out all the interactions you need to run your specs standalone if needed, but no more. If your code doesn't absolutely depend on a certain behavior, there is no need to mock it out.

Although it might be tricky to get your mocks right, they make your specs and implementation code very simple and expressive. Take the following specs, for example:

describe "A Game in progress with two players" do

 include GameStateMocks

 before :each do
   @game = Dots::Game.new
    mock_initial_game_state  
   @game.start  
 end

 it "should alternate players when a box is not completed" do
   @game.current_player.should == "Gregory" 
   stub_move([[0,0],[0,1]],0)
   @game.current_player.should == "Joe"     
   stub_move([[0,1],[1,1]],0) 
   @game.current_player.should == "Gregory" 
 end     

 it "should not change players if 1 box is completed" do
   @game.current_player.should == "Gregory" 
   stub_move([[0,0],[0,1]],1)
   @game.current_player.should == "Gregory"      
 end     

 it "should not change players if 2 boxes are completed" do
   @game.current_player.should == "Gregory" 
   stub_move([[0,0],[0,1]],2)
   @game.current_player.should == "Gregory"      
 end

end

You can see that we're stubbing out moves on the grid, clearly expressing how turn order should progress. Though the actual interactions here are simple, imagine how handy a mock could be when dealing with real-world resources you can't meaningfully test live against, such as web services. As long as you can account for the interactions you expect (return values, error conditions, etc.), you can create mock objects to mimic the behaviors and fully test your code without direct ties to those resources.

Until Next Time…

I've kept my focus mostly on exposing the interesting test bits, but what you'll find in this package is a fully functional game, albiet a little simple and fragile. Because it's broken up by iteration, you can hopefully get a good feel for the workflow. I've even implemented a simple text-based UI using HighLine for the game, which I've not covered here. Interestingly enough, this didn't require me to make many modifications at all to the rest of the code, even though I built it without a UI hooked up.

The end result looked something like this:

0   1   2   3   4   5
+ - + - + - + - + - + 5
-   -   -   -   -   -
+ - + - + - + - + - + 4
-   -   -   -   -   -
+ - + - + - + - + - + 3
-   -   -   -   -   -
+ - + - + - + - + - + 2
-   -   -   -   -   -
+===+ - + - + - + - + 1
| G |   -   -   -   -
+===+===+ - + - + - + 0

Current Player: Greg
Scores: ( Greg: 1 Joe: 0)

Draw from (x,y):

You can find this in iteration 16, runnable via play_dots.rb. See the underlying code for more details.

We'll be back soon to wrap up this exploration of BDD by looking at some of the advanced tools and techniques that experienced users leverage to improve their process. For now, hopefully you've gained a decent understanding of how things work in practice, and will have fun playing Dots and Boxes.

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 Ruby.

Copyright © 2009 O'Reilly Media, Inc.