Friday 7 November 2008

RSpec's Scenario Runner is dead - Long Live Cucumber!

Recently when browsing RSpec's homepage I noticed the bold (in the markup sense) announcement that RSpec's scenario runner has been deprecated in favour of Aslak Hellesøy's Cucumber. I have always been a fan of RSpec's scenario runner and love the plain text story support. I therefore felt compelled to see what Cucumber has to offer and I am pleased to report that it's wonderful.

First Taste of Cucumber

Having previously used RSpec's scenario runner to provide automated acceptance scenarios for a Ruby implementation of the Game of Life, I was keen to see how easy it was to migrate to Cucumber.

Cucumber can be installed with the following command:

gem install cucumber

Feature Injection

Cucumber is built around features rather than stories and recommends the Feature Injection template for describing features.


In order to [achieve value]
As a [role]
I need [feature].

I therefore revisited the create a cell story (originally found in the examples provided in the RSpec code base) and rephrased the requirement using the Feature Injection template.

Story Format


Story: Cell Creation

As a game producer
I want to create a cell
So that I can set the initial game state

Scenario:
...

Feature Injection Format


Feature: Cell Creation

In order to set the initial game state
As a game player
I need to be able to create live cells.

Scenario: Empty Grid

Given a 3 x 3 game
Then the grid should look like
"...
...
..."

Scenario: Create a single cell

Given a 3 x 3 game
When I create a cell at 1, 1
Then the grid should look like
"...
.X.
..."

Convention over Configuration

Cucumber applies a healthy dose of convention over configuration to provide a scenario runner that runs out of the box. The convention is to keep textual descriptions of features, using a .feature extension, in a features directory. Scenario steps are mapped to application code using Ruby. Step classes live in a steps directory as a child of the features directory.

For example:

   /features
/steps
game_of_life_steps.rb
create-a-cell.feature

When following the prescribed directory structure, scenarios can be executed with the following command:

       rake features

Pending Steps

Initially my game_of_life_steps.rb file did not define any steps. When executing the scenarios for the create cell feature Cucumber kindly told me which steps I needed to provide and even provided suggested implementations. This feature made me smile and was clearly developed in response to the question; how can I reduce the effort required to implement steps?

       10 steps pending

You can use these snippets to implement pending steps:

Given /^a 3 x 3 game$/ do
end

Then /^the grid should look like$/ do
end

When /^I create a cell at 1, 1$/ do
end

When /^I create a cell at 0, 0$/ do
end

When /^I create a cell at 0, 1$/ do
end

When /^I create a cell at 2, 2$/ do
end

Implementing Steps

Textual scenarios are mapped to code using a simple DSL that allows step patterns to be associated with the following step keywords: Given, When, Then. Each keyword accepts a Ruby block that will be executed when a step pattern is matched against an actual scenario step.

Steps can be parameterised using regular expressions, for example:

require "spec"

require "domain/game"
require "view/string_game_renderer"

Given /a (\d) x (\d) game/ do |x, y|
@game = Game.create(x.to_i, y.to_i)
end

When /I create a cell at (\d), (\d)/ do |x, y|
@game.create_cell_at(x.to_i, y.to_i)
end

Then /the grid should look like/ do |grid|
StringGameRenderer.new(@game).render.should eql(grid)
end

Alternatively, steps can be represented as strings using the dollar symbol to prefix a parameter.

For example:

 require "spec"

require "domain/game"
require "view/string_game_renderer"

Given "a $x x $y game" do |x, y|
@game = Game.create(x.to_i, y.to_i)
end

When "I create a cell at $x, $y" do |x, y|
@game.create_cell_at(x.to_i, y.to_i)
end

Then "the grid should look like$" do |grid|
StringGameRenderer.new(@game).render.should eql(grid)
end

Migrating away from Rspec's Story Runner

Using RSpec's story runner I used the following approach to execute my scenarios:

  1. GameOfLifeSteps class extending Spec::Story::StepGroup
  2. GameOfLifeStoryRunner class delegating to Spec::Story::Runner::PlainTextStoryRunner configured with the GameOfLifeSteps
  3. Story classes for each story that delegate to the GameOfLifeStoryRunner passing the filename of the story to execute
  4. A Rake task to execute all of my stories

At the time this did not seem too unreasonable although there was a significant learning curve figuring out how everything was configured. Cucumber solves the configuration problem using convention. Developers need to provide step definitions and Cucumber will handle the rest.

Migrating to Cucumber was largely an exercise in deleting code that was no longer required. The conversion from RSpec step definitions to Cucumber was painless as they are very similar.

Example RSpec Step Definition

require'spec/story'
require "spec"

require "domain/game"
require "view/string_game_renderer"

class GameOfLifeSteps < Spec::Story::StepGroup

steps do |define|

define.given("a $x x $y game") do |x, y|
@game = Game.create(x.to_i, y.to_i)
end

define.when("I create a cell at $x, $y") do |x, y|
@game.create_cell_at(x.to_i, y.to_i)
end

define.then("the grid should look like $grid") do |grid|
StringGameRenderer.new(@game).render.should eql(grid)
end
end
end

Readers will immediately appreciate how easy it is to convert from RSpec step definition format to the format required by Cucumber. Admittedly, my toy application is tiny in comparison to a typical production application, but I get the feeling that migrating a larger code base would not be too troublesome. The more adventurous may even wish to automate the migration process. More advice on migrating from RSpec scenarios can be found here

Steady Evolution

It is very encouraging to see the tooling around BDD evolve so that the task of mapping textual scenarios to code is now extremely simple. Certainly much easier than the previous generations of BDD frameworks. The Java community are well served by JBehave and the Ruby community now have Cucumber. Now that the technical challenges in mapping scenarios to code have largely been solved, teams can focus their efforts on collaborating with stakeholders and fellow team members to define the desired behaviour of the system being developed. After all, isn't that what BDD is all about?