Thursday, 2 September 2010

Refactoring Given/When/Then

I've come to the view that Given/When/Then is a poor way to think about scenarios, and is even worse for writing them. It makes me turn even the simplest situations into a ramble. And I don't think it's just me.

Given a user called Fred
And user Fred is logged in
And user Fred has an empty shopping basket
When Fred adds a $15 screw-driver
And Fred adds a $12 lamp
And Fred adds a $12 lamp
Then the total of Fred's bill is $39

If I start with Given/When/Then, I end up having to go through a laborious process of simplifying, getting rid of unnecessary details. In our example, above, Fred may be needed behind the scenes but doesn't add anything useful to the description.

Given we are logged in
And we have an empty shopping basket
When we add a $15 screw-driver
And add a $12 lamp
And add a $12 lamp
Then the total bill is $39

Do we need that bit about being logged in? No. Readers will likely assume it and, even if they don't, who cares anyway? It's irrelevant to what I'm trying to explain.

My rule of thumb is: if the context is unexpected (e.g. we're trying to explain what happens when you aren't logged in) then we need to mention it, but if the context can be inferred from the other parts of the scenario, or from common sense, then leave it out. There's a feeling that adding context will remove ambiguity, but all it actually does is complicate the example.

Given we have an empty shopping basket
When we add a $15 screw-driver
And add a $12 lamp
And add a $12 lamp
Then the total bill is $39

Are the words "screw-driver" and "lamp" necessary? I think they are. If we removed those words and just had the prices, the example would be harder to understand. You need something to show that the prices represent items. Using more abstract labels (e.g. "item A", "item B") is a possibility, but doesn't seem to buy us much.

What about the "empty shopping basket"? Can that be reasonably assumed? Yes, but rather than starting with an empty shopping basket, how about starting with a full one and dropping the "when" part?

Given a basket containing a $15 screw-driver
And a $12 lamp
And a $12 lamp
Then the total bill is $39

Finally let's remove the duplication.

Given a basket containing a $15 screw-driver
And 2 x $12 lamp
Then the total bill is $39

OK, this doesn't suck as much as what I started with.

But why not just write this:

1 x $15 screw-driver
2 x $12 lamp

Total bill = $39

This is what I might write on the back of an envelope if I were giving an example to somebody. With the right tools (e.g. Concordion), I can code it up exactly as I just wrote it. I don't need to use Given/When/Then. I used to think it was a good way to structure the example, but I've changed my mind.

9 comments:

Aslak Helles√ły said...

Dude, just use * - which is an alias for Given, When and Then in Cucumber.

David Peterson said...

@Aslak
Do you have an example? How would you write the scenario in this post?

What I'm really trying to argue isn't so much the way the examples are written (though I don't think Given/When/Then reads well at all), but more importantly the way that thinking Given/When/Then seems to lead to over-complicated examples.

Liz Keogh said...

I think part of the problem is that you've only got a subset of the user journey here. What happens when you actually try to get the money?

Given a basket of goods
When I go to the checkout page
Then it should give me options for Paypal or Credit Cards
When I pay using Paypal
Then I should see the receipt
And Paypal should be given the transaction.

NB: I have no idea how Paypal works, just guessing.

Given, When, Then is just a tool for thinking about context, events and outcomes. The real power comes from being able to ask whether you've missed out a particular context, or whether there are different aspects to the outcome that you're not seeing. You're looking for the differences between what's expected and what actually happens, which in your scenario you just don't have.

Of course, you can use whatever language works for you, and if you're really experienced at writing scenarios then you'll find it more useful to flex, just like any expert tool user.

I love Given, When, Then because I've seen people who are still learning this kind of thing pick it up very easily. Maybe it's just a novice practice. It still has value.

David Peterson said...

@Liz
Thanks for your reply.

Taking your example:

Given a basket of goods
When I go to the checkout page
Then it should give me options for Paypal or Credit Cards
When I pay using Paypal
Then I should see the receipt
And Paypal should be given the transaction.

Is the "checkout page" necessary? To me, it's an unnecessary implementation detail. The same goes for the option of taking credit cards. All we're trying to demonstrate here is paying with Paypal. If it helps, imagine there are two implementations: one a website and the other a headless API.

When I checkout a basket of goods
And pay using Paypal
Then I should get a receipt
And Paypal should be given the transaction.

Can we assume a "basket of goods"? I think so, unless we're specifically trying to say that "you can't check out an empty basket", but that would be covered in a different scenario.

When I pay using Paypal
Then I should get a receipt
And Paypal should be given the transaction.

I'm not totally sure what you meant by this, so it's hard to simplify further.

What I'm finding is Given/When/Then actually causes people to put in unnecessary implementation detail (maybe they feel like they have to have something in each section?) and doesn't address the issue of novices writing implementation-oriented scripts (When I enter "2" into the Quantity textbox And click the "Confirm Changes" button etc.)

Mohan Arun said...

The first "Fred's shopping basket" was more able to capture imagination than the subsequent "1 x $12 screwdriver" thing. I am reminded of the shop that had "Good fish sold here" as a sign.

- If this is a sign, shouldnt you remove the word 'here'? If not here, then where?
- Are you selling 'bad' fish? So, should you remove 'good'?
Now we have 'fish sold'
and you know how you can remove those...you are not giving fish away for free, and its 'obvious' to anyone who is able to read your sign that you are selling fish, and not something else... So should you have such a sign in the first place?

I think we need to think about when is it 'acceptable' enough when someone 'is stating the obvious'
-When it is not that long
-When the information can be comprehended without spending too much time on perceiving it
...

David Peterson said...

@Mohan
You are right, and this is why I always write a few sentences to describe the behaviour, before I give the examples.

Rather than mixing together the description of the behaviour and the examples, I state the rule and then give simple examples.

See:
http://www.concordion.org/image/concept/AnatomyOfAnActiveSpec.png

David Peterson said...

I've followed this post up with another to expand on that last comment.

http://blog.davidpeterson.co.uk/2011/01/bdd-concrete-examples-arent-enough.html

Brian Swan said...

I was intrigued by the comment about using * in cucumber so I thought I'd have a go at implementing the cut down example in cucumber. Here it is:

Feature: A simple shopping basket

Scenario: adding items

* 1 x $15 screw-driver
* 2 x $12 lamp
* Total bill = $39

The following step definition is used to implement it.

When /^(\d+) x \$(\d+)/ do |qty, price|
@basket ||= []
@basket << (qty.to_i * price.to_i)
end

Then /^Total bill = \$(.+)$/ do |expected_total|
@basket.inject(0) {|memo, item| memo += item}.should eql(expected_total.to_i)
end

David Peterson said...

@Brian
Thanks for posting that. Good to see you can at least make the example read nicely in Cucumber. Not so keen on all that regex stuff in the fixture though.