When we first wrote JBehave 1.0 we quickly recognised that there was power in the scenarios; in the conversations that they could help to drive, and in the reusability of the steps.
I loved the ease with which you could combine smaller steps to make bigger ones, use scenarios as the contexts of further scenarios, and take the necessarily procedural automated tests and turn them into sets of reusable objects.
Now BDD is more widely used, and people out there are using JBehave 2 and RSpec, and I hear complaints. Amongst them, this is one of the more common:
“Every time I change this screen, I have to go through fifteen files and add another step.”
Since JBehave uses plain text scenarios you can’t rely on the common refactoring tools, and that can make it a bit more painful than just messing with code. So, I thought I’d have a go at explaining how I avoid this issue; by sharing some of the ways in which I divide or combine contexts, events and outcomes into reusable components that help me avoid duplication in my scenarios.
Given a context
A context is a state which was set up by irrelevant forces, and which is used within the scenario to alter the outcome resulting from the events.
If the manner or time in which the context was set up matters, then that, too, is part of the context; unless it’s part of the behaviour you’re looking for, in which case it’s an event. The only reason for separating the contexts in which a scenario occurs from the events which are performed in the scenario is because it doesn’t matter how they were created. This means that Givens have more flexibility in implementation than Whens.
A context should matter. If you can remove the context from the scenario without changing the outcomes, it isn’t part of the Givens.
A context should be independent of other contexts. So, I prefer Given a wet newspaper to Given a newspaper / Given that the newspaper is wet. The first is less likely to require refactoring than the second.
A context should create an abandonable artifact. By this I mean that the forces which created the artifact – data in the database, files on the disk, a particular page at a given URL – can safely forget about the context they’ve created. Given an article about Iraq is a good context. Given I am logged in is not so good, even though we frequently use it as an example. Sorry. If it helps, it’s a step that rarely needs maintenance. Given we’ve filled in the comment box and are ready to submit it is likely to cause issues, because of all the other tiny steps that you have to use to get there.
When an event happens
An event exercises the feature whose behaviour you’re interested in when describing or running the scenario.
If you don’t care whether it works as long as it leaves things in a clean state, it’s a context. If you don’t actually need to do anything to cause an outcome – you’re simply checking that given some state, some other state is also present – you don’t need to write an event; just skip straight from Given to Then.
An event should create a valuable outcome.The granularity of the ideal event is very similar to that of the ideal context. As a user, I don’t want to go to the screen with the book, click the purchase button, navigate to the basket, enter the credit card number and click “submit”. I just want to buy a book. By specifying the steps which a user purchases a book inside the granularity of this larger step, we capture the value of that step. Since people rarely do things that they (or their sponsor, paymaster, loved one, etc.) don’t find valuable, this can usually be reused as one large step.
An event may be dependent on context or on another event. So, when I buy a book, and when I cancel my order within 15 days, then I should not be charged for the book.
An event should cause or contribute to an outcome. The outcome is something measurable. It could be that the outcome you’re looking for is an absence of something, for instance if a user’s preferences have been changed, and you no longer want to see all those Facebook groups. If it doesn’t cause an outcome, it’s a fairly irrelevant event.
Then an outcome occurs
An outcome describes the benefit that your system or application provides when the events are performed in the given context.
An outcome should have teeth. If a particular error message doesn’t have the exact wording expected, the world will not come to an end. If my credit card gets billed for the books but I don’t get them when I expect, it might.
An Outcome should be Specific, Measurable, Achievable, Relevant and Timeboxed. Ask a QA if you don’t know what this means. QAs are wise and can break anything.
An Outcome should represent the valuable purpose of the events. Instead of checking that a series of menus exist when you navigate to a particular screen, write a scenario that uses those menus and check that the benefits they provide are accessible through them.
Stories and regression tests
It can take quite a while to run scenarios. I sometimes like to turn mine into regression tests by combining them. I like to add contexts, events and outcomes to existing scenarios to better describe the benefits of using any particular feature in any particular context. This may mean that scenarios are related to more than one story. This will help keep them maintainable, and isn’t a bad thing.
I have never found the need to add a context to a scenario half-way through that scenario, even if it’s been created from several others.
I do frequently use one scenario as the context for other scenarios.
Unteaching the business
Sometimes, we accidentally train our business to talk to us about the solutions they’d like, occasionally in the language of software development. In that case, they’ll quite happily discuss the particular steps they need to take in the GUI to achieve the desired outcome, and may even have an idea of the underlying database tables and discuss what ought to be in those tables after the events.
If this happens, try to draw the conversation back to how the data will be used; why it’s valuable to produce that artifact in the first place. You never know; you may find you need to do less work than you thought you did.