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:
- GameOfLifeSteps class extending Spec::Story::StepGroup
- GameOfLifeStoryRunner class delegating to Spec::Story::Runner::PlainTextStoryRunner configured with the GameOfLifeSteps
- Story classes for each story that delegate to the GameOfLifeStoryRunner passing the filename of the story to execute
- 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?