Enabling parallel unit tests is the easiest way to accelerate your Apache Maven builds. This blog post is part of a series from the joint talk of Maarten Mulders and me: Accelerating Maven Builds: From 🐌 Snail’s Pace to 🚀 Rocket Speed.
You have multiple options to enable parallel tests: Either configure JUnit-Jupiter’s own engine, or use the options in Apache Maven’s maven-surefire-plugin if you use another test engine.
Parallel Testing with JUnit Jupiter
This is currently the most popular framework and has (in my opinion) the best configuration options. This was the first part of our joint talk. Use this option if you already use JUnit Jupiter in your project.
How to enable parallel tests in JUnit Jupiter
You have three options here:
- Using a properties file
Put a file
src/test/resources/junit-platform.properties
into each module.- Using surefire’s argLine
Add the configuration parameters as system properties to the surefire
argLine
variable.- Using surefire’s properties/configurationParameters
Add the same configuration parameters to a special (almost hidden) configuration in the surefire configuration block.
- Using annotations
Add
@Execution
annotations to each class or suite.
If unsure, start with surefire’s properties/configurationParameters like so:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<properties>
<configurationParameters>
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.mode.classes.default = concurrent
</configurationParameters>
</properties>
</configuration>
</plugin>
</plugins>
</build>
This will run both top-level classes and their methods in parallel. As a bonus, this configuration will be inherited in submodules. Therefore, your whole project will run tests in parallel.
If you want to use argLine
, the setup gets a bit more complicated.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<argLine>
@{argLine}
-Djunit.jupiter.execution.parallel.enabled=true
-Djunit.jupiter.execution.parallel.mode.default=concurrent
-Djunit.jupiter.execution.parallel.mode.classes.default=concurrent
</argLine>
</configuration>
</plugin>
</plugins>
</build>
Disabling parallel tests selectively
If you want to disable parallel tests selectively, you have a few options here.
- Overwrite configurationParameters
This is global for all tests in the current modules. Use this if a whole module’s tests cannot be run in parallel and only few modules need this exception.
- Annotation
@ExecutionMode(SAME_THREAD)
Will run the tests of the current class in the same thread, including the methods
@BeforeAll
and@AfterAll
. Use this as a first try when a single class will not run using parallel tests.- Annotation
@ResourceLock
Use this annotation on multiple classes when a resource would be shared or modified among those classes, hindering parallel execution. A typical example for a shared resource would be a global variable store, static fields accessed by multiple tests at the same time, or caches.
- Annotation
@Isolated
This annotation will run the test methods sequentially and in isolation of ALL other tests, no matter what. Use this as a last resort.
Parallel testing with Surefire
The method from above (junit.jupiter.execution.parallel
) only works for JUnit Jupiter, as JUnit Jupiter brings its own test engine. This means that you cannot use those parameters for JUnit 4, TestNG nor Spock.
This is how you enable parallel tests for JUnit 4, TestNG or Spock using Apache Maven’s surefire-plugin.
Surefire configuration options
Surefire has only a single parameter parallel
, but supports all combinations of parallelism for suites, classes and methods. These are the valid values for the parameter parallel
:
- none
Disables parallel testing completely. This is the default.
- suites
Only runs suites in parallel, but not other top-level classes or methods within any class.
- suitesAndClasses
Runs suites and top-level classes in parallel.
- suitesAndMethods
Runs suites and methods in parallel, but not any methods encountered anywhere.
- classesAndMethods
Runs top-level classes in parallel, but not suites.
- all
Runs all of it in parallel: Methods in top-level classes, top-level-classes and suites.
How to enable parallel tests in JUnit 4, TestNG or Spock
1. Enable parallel testing via XML configuration in maven-surefire-plugin
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<parallel>classesAndMethods</parallel>
<threadCount>4</threadCountSuites>
<perCoreThreadCount>true</perCoreThreadCount>
</configuration>
</plugin>
</plugins>
</build>
2. Enable parallel testing via command line for maven-surefire
mvn -Dparallel=all -DthreadCount=4 -DperCoreThreadCount=true verify
Advantages and disadvantages of configuration options
Now, obviously you cannot disable parallel testing per module using the command line - it is global. However, you could define a custom property per module to control parallel test execution per module. Since this is a tedious work and does not follow conventions, I personally would abstain from this method.
However, you can just set the configuration in a module which does not (fully) support parallel testing. Or, put the classes which do not support parallel testing into a test suite. You can also do it the other way around: Putting the tests which support parallel testing into a suite.
About forkCount
At this point, I do not advise to use fork count. Forking is relatively expensive.
Demo project: Accelerating Maven Builds
The GitHub repository mthmulders/snail-pace-rocket-speed-demos contains a script called ./demo-2-parallel-tests.sh
. Execute it to see the effects of enabling parallel tests!
Here is my outcome.
Apache Shiro without parallel tests
Running mvn verify -pl :shiro-core -am
on Apache Shiro without parallel tests takes 1:24 minutes on my machine.
Apache Shiro with parallel tests enabled
Running the same command after putting the files junit-platform.properties
into place wil only take 49 seconds!
Outlook: JUnit Looming
Christian Stein’s JUnit Looming is a showcase project wich shows how virtual tests can speed up testing. As of my knowledge (Christian may have slipped something 😉), it might get included soon into junit-jupiter.
Making the CI slower
However, please keep in mind that those options can actually hurt performance. In my CI, a few builds got slower, while the local builds on workstations were significantly faster. Reasons for this can include CPU or RAM constraints, slower file systems and more concurrent use of system resources in general. If you encounter this phenomenon, try to tune your parallelism until you find a "sweet spot" which speeds up both your local and your CI builds.
Conclusion: Parallel testing in Apache Maven
While JUnit Jupiter offers the most versatile options to parallelize your tests, a single surefire configuration can handle all the other test frameworks well. IntelliJ will sadly only pick up the property files and the annotations, which is why I use property files in some of my projects. There is a lot of room for improvement when it comes down to comparing the default configuration with a per-project optimal configuration. This is left to the educated reader as each project needs to be tuned individually.