On this website we use cookies that are strictly necessary to make our website function correctly, as well as optional – analytics, performance and/or marketing - cookies which help us improve our website by collecting and reporting information on how the website is used, as well as help us to reach out to you with information about our organization or offer. If you do not wish to accept optional cookies when visiting our website, you may change the settings in the Cookie Settings. For more information go to Cookie Settings.

Skip to content

Cucumber Parallel Scenarios with JUnit5 and Gradle

Technology

Jul 22, 2020 - 4 minutes read

Blog Cucumber Parallel Scenarios 416X305 Small
Aleksander Wolek Software Developer

Versatile developer with knowledge of many programming languages. He is a clean-code enthusiast and practitioner and appreciates simple solutions. He wants to convince the world to choose the right programming language for the problem.

See all Aleksander's posts
Dataops Ebook 416X300

Share

You are probably reading this article because your Cucumber tests are taking too long. You're tired of wasting time looking at passing logs in the console and you want to change a thing or two. In the following article I will explain how you can shorten execution of the test by parallelizing it, discuss the configuration of Cucumber, JUnit5, Gradle and the problems I came across along the way.

Since Cucumber has received support for the JUnit Platform Engine, enabling parallel scenarios has been simplified to a great extent. We will start by adding dependencies.

"io.cucumber:cucumber-java:6.0.0"
"io.cucumber:cucumber-junit-platform-engine:6.0.0"

We create a configuration file in "resources/junit-platform.properties" with the following content.

cucumber. execution.parallel.enabled=true 

If our test scenarios are in the "feature" files, we change the annotation from @RunWith to @Cucumber in our boot class. It is important to remember that the new annotation requires the test files to be under the same path (package) as the boot class.

If, after these changes, your tests are still not running in parallel, you can look at: https://github.com/cucumber/cucumber-jvm/tree/master/junit-platform-engine. The above steps are not found in Cucumber's main documentation because JUnit Platform Engine support is relatively new and the documentation has not yet been created.

After completing the configuration steps, you will rarely get beautiful green test results. Applications running on subsequent threads share many resources such as ports, database and static fields. The instances mustn’t disturb each other, so we should allocate separate resources to them.

In my case, the first thing that stopped me was using static fields to store the test state. Do you know the SharedData pattern (link) used in Cucumber to share information between the next steps of scenarios? In my project, it was used quite often, and all variables in the SharedData class were static. That's why with parallel running scenarios the data in the fields started to overwrite and, as a result, tests failed. This is not a role model, so I decided to remove it by using PicoContainer, which easily allowed to inject dependencies between step definition classes. I also removed static fields from SharedData class. Thanks to this procedure, the tests launched on many threads no longer depend on each other, and at the same time the code has become clearer and easier to read.

Even if you don't like static fields’ storing state, you still have such a field in your project - it's a logger and its configuration. I'm mentioning this to you because I’ve had tests that checked if something was logged in. Despite separate log files being assigned for each instance, when running subsequent applications on different threads, logger settings are overwritten - and as a result, all logs went to the same file. The problem was quickly resolved by removing, as it turned out, unnecessary tests. As you know, logging is not used to store important information in a text form. Files with logs are often deleted, rotated or moved to other places, which can lead to data loss. If you have the above-described tests, you are very likely to store important information that should be transferred, or otherwise your tests are simply unnecessary - as it was in my case.

The application of the previously described improvements did not result in positive tests. You also had to control the configuration of the application on subsequent threads. I created a new class called Context, where I added a ThreadLocal field with reference to a running application, and before each test I execute the Init method:

public class Context { 
    private static final ThreadLocal threadContext = new ThreadLocal<>(); 

    private LocalContext localContext; 

    public void init() { 
        if (threadContext.get() == null) { 
            threadContext.set(LocalContext.create()); 
        } 
        this.localContext = threadContext.get(); 
    } 
} 

When creating an object of the LocalContext class, the create method assigns a different configuration file with individual ports to mock services, boot port or database name for subsequent threads. The port and host of the database always remain the same, and the division into subsequent instances is done by creating multiple databases in the relational database management system.

Summary

After applying the above corrections, I got green results and the time of performing all tests on 6 threads decreased from about 8 minutes to 2 minutes. The implementation of the improvements was not trivial as the tests were not created from the very beginning with a view to running them on multiple threads. In my opinion, it brought many benefits - programmers will get feedback from CI faster and the use of server resources will be more efficient.

Dataops Ebook 416X300
Aleksander Wolek Software Developer

Versatile developer with knowledge of many programming languages. He is a clean-code enthusiast and practitioner and appreciates simple solutions. He wants to convince the world to choose the right programming language for the problem.

See all Aleksander's posts

Related posts

You might be also interested in

Contact

Start your project with Objectivity

CTA Pattern - Contact - Middle