How can I reuse my gherkin scenarios?
A Serenity user recently wrote to me with the following question:
Is there anyway way to reuse Scenarios?
Imagine that I’ve created a feature called “WelcomePage” with a Scenario called “Login” with the steps to Login to some webapp. Now, in a different feature file, on the Background part, use like a WelcomePage.Login (Feature.Scenario) and, somehow, the executing feature reuses the Scenario definition from other feature.
This was a question about Serenity BDD, but the concepts apply to all BDD implementations. So can you reuse a scenario (or feature) as a precondition for another scenario? And if you can, should you? Let’s take a look.
Each scenario tells a little story
Scenarios are little stories with a basic storyline, a bit like modern Hollywood films. They have a beginning, a middle and an end. The beginning introduces the characters and sets the scene. In the middle, some action happens. Then at the end, the actors achieve their goals and there is a conclusion.
Like a story or film, a scenario should not have a middle and an end followed by another middle and end. Only bad producers trying to milk a franchise do this.
In the example above, we have a feature called "WelcomePage" and a scenario called "Login". The corresponding scenario might look something like this:
Scenario: A user logging on the application should be taken to the welcome page Given Carlos is a registered user When Carlos logs on to the application Then he should be taken to the welcome page
This is a little story: it tells how Carlos, a registered user, logs in and is taken to the welcome page. Now we might have other scenarios related to the welcome page, such as that he should see his account balance and unread messages:
Scenario: The welcome page should show a user's account balance Given Carlos is a registered user When Carlos logs on to the application Then he should be taken to the welcome page And he should see his account balance
But this is clunky - we have already shown that Carlos goes to the welcome page in the previous scenario, so line three is just noise. Maybe we could write this scenario like this:
Scenario: The welcome page should show a user's account balance Given Carlos is a registered user When Carlos logs on to the application Then he should see his account balance
But now we are missing a key bit of information (that Carlos is on the home page), and including some details that are a bit redundant. We have already established that Carlos needs to log on, so we can safely assume this is the case. Let’s see if we can simplify this a bit:
Scenario: The welcome page should show a user's account balance Given Carlos is a registered user When Carlos is on the home page Then he should see his account balance
In this case, the step definition for "When Carlos is on the home page" would include a check to ensure that Carlos has indeed logged on to the application.
And if you wanted to describe several aspects of the home page, you could use a
Background clause like this:
Background: Given Carlos is a registered user And Carlos is on the home page Scenario: The welcome page should show a user's account balance Then he should see his current account balance Scenario: The welcome page should show the current logged in user id Then he should see his user id in the header ...
Note that scenarios within a feature file are independent - the scenarios in a feature are not meant to be read as a sequence of actions (like episodes of a mini-series) but as independent stories on a related topic (like James Bond films - they all figure the same central character, but the stories are independent of each other). In particular, you should be able to run the scenarios in any order.
Now to get back to the original question - how would you reuse the original scenario "A user logging on the application should be taken to the welcome page" in other scenarios? The answer is, we should never need to. Reuse happens under the hood, in the step definitions. Steps like "And Carlos is on the home page" will call the same underlying code components used by the step definitions for "When Carlos logs on to the application", but readability is king.
Step definition methods are glue code - they stick things together
Step definition methods are known as "glue code"; this is because they "glue" the step definition phrases to the code that actually does the work. Let me emphasise this point.
Glue code shouldn’t do any direct work. Step definition methods shouldn’t call WebDriver, or invoke a web service, or query a database. They should delegate this work to other, more reusable code.
So reuse comes from the underlying components and individual steps, not from whole scenarios or features. In standard Serenity, for example, this would be done with step libraries. With the more modern Screenplay tests, you would use the more flexible task and interaction classes. But in all cases, the step definition code simply calls other components, and it these components that provide the real reusability.
The reason this sort of question arises is that people often confuse BDD scenarios with test scripts. A BDD scenario is not a test script; it is an executable specification, which is quite a different beast. Using tools like Cucumber to write test scripts almost always results in tests that are hard to read and harder to maintain.