Solving: How to call one scenario from another? #GivenWhenThenWithStyle

The challenge for this week was to remove duplication from similar scenarios, in particular when they share groups of steps. See the original post for a detailed description of the problem. This article contains two ways of solving the problem. The first approach almost implements the proposed solution, calling the steps of one scenario from another. The second approach provides a much better way of solving the same problem.

Once a team builds up a library of useful Given-When-Then steps, testers or analysts can often write new scenarios that require little or no developer involvement to automate. Scenarios with shared steps usually call the same step bindings. A step that’s worded the same, or very similarly, in two different scenarios will typically have a single implementation, as in the picture below:

This is great for individual steps, but once scenarios start to use similar blocks of steps, the duplication starts to hurt. People who maintain the Given-When-Then feature files then usually look for some way of sharing entire blocks of steps, for example one scenario executing another.

This kind of sharing is not directly possible with SpecFlow. To the best of my knowledge, no other Given-When-Then tools support this kind of sharing.

This might sounds like a missing key feature, but it’s not. In fact, it would be a very bad practice to compose scenarios that way. However, let’s first look at how to implement something close to the proposed solution.

Replace blocks with a more descriptive step

From an analysis perspective, scenarios should be focused on what’s being tested and not how a test is executed. Very often, when there are entire blocks of steps shared between scenarios, the steps are describing the mechanics of testing instead of the purpose of a test. There’s usually a higher level concept in play. In the original problem post, almost all scenarios share a block of steps that ensures a user is signed in:

Given a visitor opens the homepage
And the visitor enters a new username into the registration form
And the visitor enters a valid password into the registration form
When the visitor submits the registration form
And the visitor is redirected to the login form
And the visitor enters the username and password into the sign in form
When the visitor submits the sign in form

Instead of calling one scenario from another, it would be better to create a higher level concept and use it everywhere instead of that block. For example, let’s just create a step that sets up a session for a logged in user:

Given a user is logged in

We can then implement this step by reusing the automation components in a lower level.

Resist the urge to call steps from other steps

There are several ways of implementing higher-level steps. One common, but mistaken approach, would be to just call lower-level steps from higher-level steps. SpecFlow does support such calls, allowing us to to implement the sharing just below the feature file layer, in the step implementations.

This kind of abstraction is easy to implement initially. Instead of making the implementation for a higher level step connect to the system under test, it can invoke other steps. The implementation could in theory look as in the snippet below:

[Given(@"a user is logged in")]
public void GivenUserIsLoggedIn()
  Given("a visitor opens the homepage");
  Given("the visitor enters a new username into the registration form");
  Given("the visitor enters a valid password into the registration form");
  Given("the visitor submits the registration form");
  Given("the visitor is redirected to the login form");
  Given("the visitor enters the username and password into the sign in form");

The problem with this approach is that we’re simulating sub-procedures, and effectively programming in a language that’s not designed for code. In simple cases this might be OK, but for complex situations this quickly leads to an unmaintainable mess. The approach is error-prone, and problems are difficult to spot and fix.

For more information on this feature, check out the Calling Steps from Step Definitions page in SpecFlow documentation. However, I would not recommend using this approach. Neither would the people who maintain SpecFlow. This feature is deprecated, and will be removed from one of the upcoming SpecFlow releases, exactly because it leads to a bad unmaintainable mess.

Avoid programming in Given/When/Then. Use a programming language for that. You’ll get good compilation support, type checking, and all other benefits of modern programming languages.

Create test utilities for shared code

One of the best things about SpecFlow and similar tools is that they separate the system under test from the textual scenarios using a very thin layer of code. This layer should be thin, but it doesn’t have to be just one level deep.

The right way to share implementation between different steps would be to introduce another level of code. Instead of calling steps through SpecFlow, create a utility class that captures user interactions with the login form, and then directly manipulate it from various step implementations.

Utilities such as interface drivers and page objects belong to this intermediate layer between step implementations and the system under test. Don’t expose user interactions directly to Gherkin steps, as that leads to using an automation language in specifications, instead of focusing on clearly describing the problem domain. Encapsulate them into something meaningful for your product, and simplify the code in the steps.

These utility classes can become relatively complex for large systems, and that’s perfectly fine. It’s better to manage test workflow complexity in code than in plain text. You can keep removing duplication and extracting layers. In case of user-interface tests, I usually like to split out at least three levels: business rules, workflows and technical activities. Different business rules might reuse parts of different workflows (such as the sign-in process or purchasing), and various workflows might use the same technical components in different ways (for example the shopping cart or the login form). For more information on this approach, check out my article How to implement UI testing without shooting yourself in the foot.

As a side note, sometimes these utility classes become useful for other types of tests, for example for technical integration tests, or as quick drivers for component tests. This significantly increases the value of that middle testing layer, and provides an even higher incentive to abstract things away from SpecFlow step implementations.

Be careful about what you reuse

Finally, I’d like to leave you with a guideline to know when to reuse steps, and when not.

There are two typical situations with reusing steps, and that it’s important to spot which one you’re dealing with.

When the step library reflects the business domain, sharing steps across scenarios promotes using a ubiquitous domain language, and helps teams achieve better understanding of the problem they are trying to solve.

On the other hand, when the shared steps work as low-level automation components, the same approach leads away from ubiquitous domain language, and causes a long-term maintenance headache.

Whenever you get the urge to share steps across scenarios, consider whether you’re doing it because the steps really fit the problem space, or if they’re just low-level automation components. If it’s the latter case, it’s always better to create an intermediate automation utility layer and push the reuse there.

Next challenge

Check back tomorrow for the next challenge. Meanwhile, we would appreciate it very much if you could fill in a quick feedback form about this series of articles, so we can make it better for you in the future.

The next Given-When-Then with style challenge is about creating good scenario titles.

Stay up to date with all the tips and tricks and follow SpecFlow on Twitter or LinkedIn.

PS: … and don’t forget to share the challenge with your friends and team members by clicking on one of the social icons below 👇