advertisement

Print

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

Pages: 1, 2, 3, 4

Next Pagearrow