Joseph Wilk

Joseph Wilk

Things with code, creativity and computation.

Telling a Good Story - Rspec Stories From the Trenches

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:

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<span style="text-decoration: line-through;">
</span>
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

Comments