BT

RSpec Adds Eagerly-Awaited RBehave Functionality for Integration Testing

by Sean Miller on Oct 31, 2007 |
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.

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Interested to Try This 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.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

1 Discuss

Educational Content

General Feedback
Bugs
Advertising
Editorial
InfoQ.com and all content copyright © 2006-2014 C4Media Inc. InfoQ.com hosted at Contegix, the best ISP we've ever worked with.
Privacy policy
BT