Telling a good story – Rspec stories from the trenches
15 Aug
I’ve been developing multiple systems using Rspec stories for a little while now. There are a lot of great resources to get you started with a taste of what you can do with stories. Some of the resources I found useful where:
- http://peepcode.com/products/rspec-user-stories
- http://blog.davidchelimsky.net/2008/6/16/slides-from-railsconf
- http://www.benmabey.com/2008/02/04/rspec-plain-text-stories-webrat-chunky-bacon/
- http://dannorth.net/whats-in-a-story
- http://evang.eli.st/blog/2007/10/8/story-runner-top-to-bottom-screencast
However once I had understood the basic idea I struggled to find practical examples and general guidance on writing real stories. So I’ve collected some of the lessons I’ve learnt along the way with story examples taken from real systems and how I’ve improved them as I learnt. Most examples are from web based applications.
There are no absolutes in these lessons, they are general rules. If the customer is NASA and they want an extremely explicit and low level story then that is fine. Really stories are the customers expressions of requirements and each customer may want to express these in different ways.
On the other hand it is often you helping the customer take those first baby steps into using stories. They turn to you for guidance until they can stand on their own two feet.
Stories are not specs!
Specs are your unit tests, they’re fast and lean and always running, helping you refactor and develop your code. They describe the system at the object level (and dependent on how you mock/stub they do this in isolation). Stories are like integration tests, they document how your system works. They help you know that your system meets the customers requirements.
Your spec test is going to care about testing each action in your controller.
Your story is going to care about usage of the system cutting through models/controllers/views.
Hence specs tend to have a high coverage while stories cover only that which is important to the customer!
Some people do run their stories continuously like their specs (which is only really practical if you are using only Webrat). Generally I only run my stories before a commit and I also have them running on a continuous integration server (CruiseControl.rb) on every commit.
Keep the story goals as real values for the customer
Story: Search As a website user I want a search box So that I can enter terms and click search
Why does the website user want to enter terms and click search?
To find things!
Story: Search As a website user I want to search So that I can find content on the website
This is where what David Chelimsky mentions as ‘popping the “why”? stack’ helps us keep focused on real customer values.
Stories should not be exhaustive
Story: Changing pages urls As a website user I want to change the URLs of pages So that I can improve any bad urls Scenario: valid url Scenario: blank url Scenario: symbol url Scenario: character url Scenario: numeric url
I find here we can get a bit more generic and concentrate of what is really important in this story. There are two different behaviours valid and invalid. (This does bring up the idea that stories can follow a behavioural or stateful approach, which I’ll leave as a discussion for another time)
Story: Changing page urls As a website user I want to change the URLs of pages So that I can improve any bad urls Scenario: invalid url Given a title which is invalid in a url When I save Then it should show me an error message telling me why it was invalid Scenario: valid url Given a title which is valid in a url When I save Then it should not show me an error message
This serves as a good base for the client to add to and refine as their requirements evolve. I prefer the stories to facilitate discussion rather than being documentation which starts to happen when you get too exhaustive.
Stories should speak with the customers domain terminology
Ideally your stories should be written by the customer in their terminology. Even if you don’t have direct access to a customer try putting yourself in their shoes.
With web applications if you find yourself mentioning things like ‘database’, ‘views’, ‘controllers’, ‘models’ and ‘sql’ your writing like a programmer and not an average customer (Well that’s what I’ve generally found).
In the previous example note how we saved a whole lot of work by saying:
'valid in a url'
Rather than defining explicitly the different rules for what is a valid url in the story. So using the domain terminology can help make your stories more concise.
Stories should not be too low level.
Story: Viewing homepage As a logged in user I want to view my homepage So that I will see it Scenario: login Given I'm logged in When I visit my homepage Then I will see it
If it takes you longer to write the story than complete the task its too small. When you end up with a couple of stories which are too small general its a good idea to combine them to form a more reasonably sized story. In this case I took the Viewing homepage story and combined it with a set of small stories:
Story: Important pages are accessible
As a logged in user
I want to visit key pages in the site
So that I can successful navigate around it
Scenario: Homepage
Scenario: Login page
Scenario: Payment page
Stories should not be too high level.
Stories that are too large are referred to as Epic stories. Epics tend to occur when the story scenarios are trying to be too exhaustive or the story is a:
- Compound story – comprises multiple shorter stories
- Complex story – Cannot easily be broken down into smaller stories
An example of an epic:
Story: Manage Content As a admin user I want to manage my sites pages So that I can keep my website up-to-date Scenario: Add a page Scenario: Delete a page Scenario: Edit a page
I find a good guideline is that a story should be accomplishable in around 1 week.
There are two ways you can split a epic story into snappy short stories:
Split Along create, add, edit and delete.
or
Disaggregate along the boundaries of the data
So initially I went with disaggregating along the data boundaries but still found the stories where looking big, so I then tried splitting on actions which left me with some nice snappy stories.
Story: Add page As a admin user I want to add pages So that I can keep my website up-to-date Scenario: ... Story: Delete page As a admin user I want to delete pages So that I can remove bad or old articles Scenario: ... Story: Edit page As a admin user I want to edit pages So that I can refine my articles as time passes Scenario: ...
Note how our understanding of the users goals has expanded here. We know in more detail what the user wants from the system.
When choosing which way to split stories I often write out what the stories and scenarios would look like which helps me get a feeling for which splitting method is best suited.
Stories should concentrate on outputs not inputs
Story: Creating an account As a new user to the site I want to create a account for the site So that I can login and comment on articles Scenario: valid account details Given I'm logged out And I've entered my username 'spoink' And I've entered my website 'http://www.joesniff.co.uk' And I've entered my email address 'spoink@joesniff.co.uk' And I've entered my password 'poodle' And I've entered my password 'poodle' And I've entered a password reminder 'not a monkey' And I've selected 'do not send me emails' And I've selected 'I agree to terms and conditions' When I click the create account button Then my account will be created And I will be redirected to my account page
Outputs represent value to the customer. They tend to be far more interested in what they get out of the system than what they put in.
Story: Creating an account As a new user to the site I want to create a account for the site So that I can login and comment on articles Scenario: valid account details Given I'm logged out When I create a new account Then I will be redirect to my account page And I will be presented with the last time I logged in And my current community ranking And the last comment I made.
In this example focusing on all the details of creating an account while useful missed the fact that the most important thing to the client was why the user was creating the account and what benefit/output they got that kept them there.
Stories should slice through multiple layers
Stories act as integration tests cutting through all the layers of your web application stack.
If you only cut through certain layers you introduce the possibility that the stories pass without parts of your system working!
Story: Fill in form As a logged in user I want to fill in a register form So that I can register Story: Save in database As a logged in user I want my form information saved in the database So that the system remembers me
Yuck! Hearing the user talk about ‘the database’ is a sure sign these stories have problems.
Stories should be kept organised
I tend to use one of two ways of organising my stories in the file system:
1. Organise by Stakeholder
Contain the stories based on which stakeholder is acting in the story.
stories/
customer/
purchase.story
purchase.rb
web_master/
...
advertiser/
...
2. Organise by Feature
Contain the stories based on which feature stories relate to. (This is particularly useful if you’re structure follows Epic -> Feature -> Story ).
stories/
page_editing/
edit_page.story
edit_page.rb
image_uploading/
...
login
...
Use GivenScenario to make Stories easier to read and discuss
GivenScenario allows you to reuse scenarios which can help make the stories clean and in turn easier to discuss with the customer.
Story: maintaining blog As a web admin I want to manage pages within an admin system So I can keep my blog up to date Scenario: Add page ... Scenario: edit page with valid details GivenScenario: Add page When I edit pages details Then it should save without errors
Stories should not leave hidden magic in steps
When scenarios start to fail in your stories its often up to the developer to have a look why. You often turn to the story to check the steps.
... Given there is a page 'testing' ...
Given(/there is a page '(.+)'/) do |title|
@page = Page.create!({ :title => title)
@page.images = Image.create!({ :test )
end
Misleading or not revealing hidden steps can making debugging stories a painful process.
I changed the story to make sure it was obvious what was going on:
... Given there is a page 'testing' with an image 'test' ...
Make your Given/When steps fail loudly
Stories can be hard to debug. Anything in the Given & When could have failed but we are only aware of a Then failing. So we have to search back through the Givens and Whens to spot an error.
The following could fail but the step would pass.
Given('there is a page') do
Page.create {:title => 'explode early'}
end
By throwing an exception we can ensure that if anything goes wrong the step fails
Given('there is a page') do
Page.create! {:title => 'explode early'}
end
Avoid relying heavily on instance values in steps
Relying on using instance values makes it difficult to reuse your steps. Also Interdependencies between steps can make them harder to debug.
Given('there is a page') do
@page = Page.create! {:title => 'dont do it!'}
end
-
Nigel Thorne >> Blog >
-
Joseph Wilk
-
Ichsan
-
Ben Mabey
-
Joseph Wilk
-
David Chelimsky
-
aidy lewis