User Stories are a great way to plan your work. You can take a big hairy requirement and break it down into chunks that are small enough to work on without anyone freaking out. When you’ve crumbled up your big hairy requirement into little user story chunks, you can pick and choose which chunk to build first, and even drop some chunks altogether when you realise they’re not that important. Great stuff.
So we like user stories. They’re our friends. Here’s one:
In order to pay for the items in my shopping cart, As a customer, I want a credit card checkout
Now, suppose when we do the analysis for this story we discover that certain types of credit cards are much easier to hook up to a checkout than others. We want to ship something as soon as possible, so that people can start giving us money. So we split the story in two.
One for the friendly FooCorp credit card:
In order to pay for the items in my shopping cart, As a customer with a FooCorp credit card, I want a credit card checkout that accepts my card
And another for the altogether-much-harder-to-integrate-with BarCorp credit card:
In order to pay for the items in my shopping cart, As a customer with a BarCorp credit card, I want a credit card checkout that accepts my card
As everyone knows, BarCorp have some really outdated systems, and connecting to their payment processing is a right old pain. We’ll leave that for later.
So let’s get on with implementing our first story. We’ve done some analysis on it, but let’s make sure we understand the acceptance criteria. We have a little sit-down with our product owner, and come up with this list:
- when a user attempts to pay with a BarCorp card, we show them a “Sorry” message
- when a user pays with a FooCorp card, we send them an email with their receipt
- when a user attempts to pay with a FooCorp card but gets their details wrong, we bounce the page and show them an error message, so they can correct their mistake.
- when a user attempts to pay with a FooCorp card but the card is declined, we bounce the page and show them an error message, so they can try another card.
So those are the acceptance criteria for this story. We didn’t do anything fancy with them like put them in a Microsoft WordÂ® document or a Jira ticket. We just wrote them down on a piece of paper.
Now we’ll use Cucumber to document these acceptance criteria as Scenarios that we can use to drive out the behaviour we need to get this story done.
Let’s start by creating a new feature:
Whoops. You made a mistake. Did you spot it?
That’s right, you went and got confused and thought that the user story you were implementing now was the same thing as the feature. It’s not.
Right now, this distinction probably seems a bit nit-picky. But wait until you’ve got around to implementing BarCorp’s credit card check-out, and BazCorp’s too. Your features directory is going to look like this:
features/ pay_with_foocorp_card.feature pay_with_barcorp_card.feature pay_with_bazcorp_card.feature
Can you see where this is going? Give it a few months, and your features directory is going to look like a history log of the development of your project. Hey, while you’re at it, why not put the jira ticket in the filename, or even use a tool to synchronise the feature files with Pivotal Tracker?
I see lots of people doing this, and trust me, it’s an anti-pattern.
Before there was Cucumber, there was the RSpec Story Runner. There was also this blog post. That was a long time ago. When Aslak created Cucumber, he renamed the files from
.feature. This wasn’t an accident or an idle act of whimsy: it’s because there’s a difference.
User Stories are a planning tool. They exist until they’re implemented, and then they disappear, absorbed into the code.
Cucumber features are a communication tool. They describe how the system behaves today, so that if you need to check how it works, you don’t need to read code or go punching buttons on the live system. Organising your features according to the way they were planned and implemented is a distraction from this purpose.
Imagine if the user manual for your washing machine was organised by how the washing machine had been constructed? A nonsense.
Going back to our example, let’s rename our feature:
mv features/pay_with_foocorp_card.feature features/pay_with_credit_card.feature
Now that’s more like it. As we implement the first User Story for FooCorp credit cards, we’ll put some scenarios into this feature. When we come to implement BarCorp support, we can add a couple more scenarios to the same feature. We might even have to modify existing scenarios, perhaps if we add a new widget to the UI to let the user choose their credit card type.
The User Story is absorbed into our features and becomes invisible, leaving the features as a live document of what the software does now, not as a record of how it was constructed.
Thanks to Paul Wilson for reminding me to write this post.