InfoQ

News

RSpec Adds Eagerly-Awaited RBehave Functionality for Integration Testing

Posted by Sean Miller on Oct 31, 2007 07:59 AM

Community
Agile,
Ruby
Topics
Delivering Quality ,
Customers & Requirements
Tags
BDD ,
RBehave ,
RSpec ,
Testing
RSpec is a Behaviour-Driven Development (BDD) acceptance testing framework written for Ruby and also usable (since it can run under JRuby) for Java. Historically it has needed an add-on extension to handle story-level integration tests, but David Chelimsky has now added a Plain Text Story Runner directly into RSpec, incorporating RBehave functionality.

RSpec provides a mechanism by which developers can take acceptance specifications from the business and turn them into readable and executable examples that can serve as documentation, tests, and business-readable build reports. While RSpec is useful for unit-level tests, it did not support story-level integration tests out of the box. Dan North built a separate extension, RBehave, to describe behaviour at the story level, as a series of steps of the form Given ... With ... Then ... . (North first described this pattern for capturing story requirements in the context of JBehave.)

David Chelimsky has now incorporated into the RSpec trunk a Plain Text Story Runner that gives RSpec the functionality of RBehave, as described in his blog.

So, North's classic RBehave example:

require ‘rubygems’
require ‘rbehave’
require ’spec’ # for "should" method

require ‘account’ # the actual application code

Story "transfer to cash account",
%(As a savings account holder
  I want to transfer money from my savings account
  So that I can get cash easily from an ATM) do

  Scenario "savings account is in credit" do
    Given "my savings account balance is", 100 do |balance|
      @savings_account = Account.new(balance)
    end
    Given "my cash account balance is", 10 do |balance|
      @cash_account = Account.new(balance)
    end
    When "I transfer", 20 do |amount|
      @savings_account.transfer_to(@cash_account, amount)
    end
    Then "my savings account balance should be", 80 do |expected_amount|
      @savings_account.balance.should == expected_amount
    end
    Then "my cash account balance should be", 30 do |expected_amount|
      @cash_account.balance.should == expected_amount
    end
  end

  Scenario "savings account is overdrawn" do
    Given "my savings account balance is", -20
    Given "my cash account balance is", 10
    When "I transfer", 20
    Then "my savings account balance should be", -20
    Then "my cash account balance should be", 10
  end
end

can become, in the new RSpec, a Ruby file to define the available steps:
 
class AccountSteps < Spec::Story::StepGroup
  steps do |define|
    define.given("my savings account balance is $balance") do |balance|
      @savings_account = Account.new(balance.to_f)
    end

    define.given("my cash account balance is $balance" do |balance|
      @cash_account = Account.new(balance.to_f)
    end

    define.then("my savings account balance should be $expected_amount" do |expected_amount|
      @savings_account.balance.should == expected_amount.to_f
    end

    define.then("my cash account balance should be $expected_amount" do |expected_amount|
      @cash_account.balance.should == expected_amount.to_f
    end
  end
end

steps = AccountSteps.new do |define|
  define.when("I transfer $amount") do |amount|
    @savings_account.transfer_to(@cash_account, amount.to_f)
  end
end

a plain text file that defines the story behaviour in terms of those steps:

Story: transfer to cash account
  As a savings account holder
  I want to transfer money from my savings account
  So that I can get cash easily from an ATM

  Scenario: savings account is in credit
    Given my savings account balance is 100
    And my cash account balance is 10
    When I transfer 20
    Then my savings account balance should be 80
    And my cash account balance should be 30

  Scenario: savings account is overdrawn
    Given my savings account balance is -20
    And my cash account balance is 10
    When I transfer 20
    Then my savings account balance should be -20
    And my cash account balance should be 10
 
and a Ruby file to glue them together and run the story:

require 'spec'
require 'path/to/your/library/files'
require 'path/to/file/that/defines/account_steps.rb'

# assumes the other story file is named the same as this file minus ".rb"
runner = Spec::Story::Runner::PlainTextStoryRunner.new(File.expand_path(__FILE__).gsub(".rb",""))
runner.steps << AccountSteps.new
runner.run

The wording of the steps in the plain text file has to match the steps defined in the StepGroup, which could become unwieldy with large numbers of steps. To make it easier, Aslak Hellesøy is working on a browser-based editor, with autocompletion for steps and in-place editing of parameters.

Related Sponsor

VersionOne is recognized by Agile practitioners as the leader in Agile project management tools. Companies such as Adobe, BBC, CNN, Dow, HP, IBM, Sony and 3M have turned to VersionOne to help deliver greater value to their customers.
Interested to Try This by Geoffrey Wiseman Posted Oct 31, 2007 12:18 PM
  1. Back to top

    Interested to Try This

    Oct 31, 2007 12:18 PM by Geoffrey Wiseman

    Looking forward to trying this; I'm still not sure if I prefer the mixed-code-and-text model that existed before (earlier versions of Story Runner, and possibly RBehave before that), or the separation achieved here. If the steps end up being relatively re-usable and you are able to get business users (on-site customers, program managers, whatever) to write stories this new format seems well-suited for that. OTOH, if developers are end up writing one story in two files with not a lot of re-use, it seems like this structure will just get in the way. Either way, curious to try it 'for real' and form an opinion.

Educational Content

Bindings, Platforms, and Innovation

This presentation focuses on the Internet and separating myth from fact, history from the future, and the mundane from the imaginative. Bob Frankston presents a vision of what could and should be.

Orchestrating Long Running Activities with JBoss / JBPM

This article explores the use of JBoss and jBPM to implement design solutions that effectively address the issue of orchestrating long running activities.

Neo4j - The Benefits of Graph Databases

This presentation covers the use of graph databases as an optimal solution for data that is difficult to fit in static tables, rapidly evolving data or data that has a lot of optional attributes.

Realistic about Risk: Software development with Real Options

This session introduces Real Options and shows how it can help in running your project. Real Options is a decision-making process that can be used to manage risk.

Communication Flexibility Using Bindings

This article discusses the use of bindings on services and references (including the instance of non-configured bindings) as the means to implement SCA communications in a Web and SOA environment.

Writing DSLs in Groovy

After a short introduction to DSLs, Scott Davis plays with the keyboard showing how to approach the creation of a DSL by typing working snippets of Groovy code that get executed.

Scaling Agile with C/ALM (Collaborative Application Lifecycle Management)

IBM Rational and InfoQ present, Scaling Agile with C/ALM, an eBook showing organizations how to become “finely tuned software delivery machines” by enabling team integration and scaling.

Concurrent Programming with Microsoft F#

Amanda Laucher presents a real life enterprise application written in F#. She shows actual code snippets, explaining design decisions and suggesting how to use some of the F# constructs.