(first posted: Sep 25, 2007)
(6166 Reads)
keywords: rspec story runner
Permalink
Beginners Guide to Rspec on Story Runner
Story Runner is a nice behavioral integration "test" (er, uh, spec) tool that been incorporated into Rspec and can be used in Ruby on Rail projects. Its not officially released with Rspec but is available in trunk.
References:
http://rspec.rubyforge.org - Rspec home
http://rubyforge.org/pipermail/rspec-devel/2007-August/003756.html - David Chelimsky's announcement
http://evang.eli.st/blog/2007/9/1/user-stories-with-rspec-s-story-runner - good tutorial
http://www.nabble.com/Getting-Started-with-Story-Runner-t4485820.html - a helpful discussion thread
http://dannorth.net/whats-in-a-story - explains the structure of a "story"
http://pastie.caboo.se/92472 - a famous pastie (fame is relative :)
http://dannorth.net/2007/06/introducing-rbehave - where it all started
Installation
Assuming you already have a Rails app going, install Rspec from trunk:
$ ruby script/plugin install svn://rubyforge.org/var/svn/rspec/trunk/rspec
$ ruby script/plugin install svn://rubyforge.org/var/svn/rspec/trunk/rspec_on_rails
$ script/generate rspec
Create a directory in your web root named "stories/"
Create a file stories/story_helper.rb with the following:
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require 'spec/rails/story_adapter'
Each story script is an .rb file with the following as its first line:
require File.join(File.dirname(__FILE__), "story_helper")
To run a story, just run it directly:
$ ruby story/mystory.rb
Thinking
From Dan North's article, the general idea is you spec out a conversation about a feature or requirement. There's an action title, a text narrative, and then a set of scenarios. The scenario should be described in terms of Givens, Events and Outcomes (given/when/then...)
The structure is basically like this:
Title (one line describing the story)
Narrative:
As a [role]
I want [feature]
So that [benefit]
Acceptance Criteria: (presented as Scenarios)
Scenario 1: Title
Given [context]
And [some more context]…
When [event]
Then [outcome]
And [another outcome]…
Scenario 2: …
A very simple first story
The first thing I do when describing a site to someone is go to the home page, and being exploring from there. So, that seems like a legitimate first story to spec out. I create a file stories/anonymous_visitor.rb
require File.join(File.dirname(__FILE__), "story_helper")
Story "Anonymous visitor to the site", %{
As an anonymous visitor
I want to visit public pages
}, :type => RailsStory do
Scenario "Starts at home page and drills down" do
When "visiting home page" do
get "/"
end
Then "user should see home page" do
response.should render_template('base/home')end
When "he clicks on the About link" do
get "/about"
end
Then "user should see the About page" do
response.should render_template('base/about')end
end
end
OK, maybe that's not so practical (and really should covered in the corresponding controller spec), at least you can make sure your stories are running and good to go. (And after all, Rails' default unit test is simply def test_truth; assert true; end)
But then I start building on that, either with more clauses, new scenarios, and/or starting another story file. What about a user logging in? then he should see different content. And when he creates a post, then... And... And, suddenly you're off and (story) running.
Parameterized Scenario Clauses
You can reuse scenario clauses, and pass parameters. For example,
Scenario "savings account is in credit" do
Given "my savings account balance is", 100 do |balance|
@savings_account = Account.new(balance)
end
And "my cash account balance is", 10 do |balance|
@cash_account = Account.new(balance)
end
Then the next scenario can reuse the Given clauses with different arguments,
Scenario "savings account is overdrawn" do
Given "my savings account balance is", -20
And "my cash account balance is", 10
Going one step further (as of today Oct 22 '07), Rspec trunk introduces Plain Text Scenarios, which integrates parameters into the text. See http://blog.davidchelimsky.net/articles/2007/10/21/story-runner-in-plain-english Doing so, stories become "block-less"-- it encourages you to put all your step clauses into separate Step Matcher methods, which means we can now eliminate all the quote and write truly plain text stories (!!). It's still experimental, but given the pace this stuff is moving, it may be mainstream by the time you read this!
Scenario: savings account is in credit
Given my savings account balance is 100
And my cash account balance is 10
Working with Stories
I've found that writing stories (and their multiple scenarios) helps me think through how things should work (and not work) on the site.
When I implement a scenario, the "Given" clauses often correspond to models, which leads me to write a model spec, and then implement the model methods.
And perhaps some actions (especially POSTs that affect models or the session), so I spec their views and controllers. And implement them.
Usually by then I've lost my context so I'll go back to the current story and see where I'm at in the scenario.
The last Given is often a GET action (which provides visual context for the upcoming When clause), so I go ahead and do its view and controller specs.
The "When" clause is the meat of the scenario. It is a controller action, and its spec gets done next, which often requires drilling down into other models, views, and helper methods.
Then, the "Then" clauses tend to assert the response in a view. They'll probably also call model methods to check for results. They get spec'd and implemented, and so on.
As the stories grow more complex, previously spec'ed scenarios might be stuffed into the Given section of the next story. At that point I write reusable helper methods, which pulls the preconditions together, such as
Given "a user is logged in and has created a post" do
user_is_logged_in_and_has_created_a_post
end
In stories, don't use mocks, stubs, or fixtures, as you want to exercise the whole stack. Instead I found the fixture_replacement plugin to be an extremely handy model factory.
One other thing, the clauses in a scenario may be in a specific order that makes sense from a story point of view, but not necessarily the order I want to implement. But (presently) story-runner just stops when it encounters a pending (unimplemented) clause. I've added this little method to my story_helper.rb
def skip_pending(text='')
puts " SKIP PENDING #{text}"
end
which might be used like this:
And "a welcome message is sent to user" do
skip_pending "email notifications"
end




Beginners Guide to Rspec on Story Runner
Posted by: Scott Taylor on October 23, 2007 01:56 AM#