2.19. Parallel Execution

Parallel test execution is an experimental feature
You’re invited to give it a try and provide feedback to the JUnit team so they can improve and eventually promote this feature.

By default, JUnit Jupiter tests are run sequentially in a single thread. Running tests in parallel — for example, to speed up execution — is available as an opt-in feature since version 5.3. To enable parallel execution, set the junit.jupiter.execution.parallel.enabled configuration parameter to true — for example, in junit-platform.properties (see Configuration Parameters for other options).

Please note that enabling this property is only the first step required to execute tests in parallel. If enabled, test classes and methods will still be executed sequentially by default. Whether or not a node in the test tree is executed concurrently is controlled by its execution mode. The following two modes are available.

SAME_THREAD

Force execution in the same thread used by the parent. For example, when used on a test method, the test method will be executed in the same thread as any @BeforeAll or @AfterAll methods of the containing test class.

CONCURRENT

Execute concurrently unless a resource lock forces execution in the same thread.

By default, nodes in the test tree use the SAME_THREAD execution mode. You can change the default by setting the junit.jupiter.execution.parallel.mode.default configuration parameter. Alternatively, you can use the [@Execution](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Execution.html) annotation to change the execution mode for the annotated element and its subelements (if any) which allows you to activate parallel execution for individual test classes, one by one.

Configuration parameters to execute all tests in parallel

  1. junit.jupiter.execution.parallel.enabled = true
  2. junit.jupiter.execution.parallel.mode.default = concurrent

The default execution mode is applied to all nodes of the test tree with a few notable exceptions, namely test classes that use the Lifecycle.PER_CLASS mode or a [MethodOrderer](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.html) (except for [Random](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Random.html)). In the former case, test authors have to ensure that the test class is thread-safe; in the latter, concurrent execution might conflict with the configured execution order. Thus, in both cases, test methods in such test classes are only executed concurrently if the @Execution(CONCURRENT) annotation is present on the test class or method.

All nodes of the test tree that are configured with the CONCURRENT execution mode will be executed fully in parallel according to the provided configuration while observing the declarative synchronization mechanism. Please note that Capturing Standard Output/Error needs to be enabled separately.

In addition, you can configure the default execution mode for top-level classes by setting the junit.jupiter.execution.parallel.mode.classes.default configuration parameter. By combining both configuration parameters, you can configure classes to run in parallel but their methods in the same thread:

Configuration parameters to execute top-level classes in parallel but methods in same thread

  1. junit.jupiter.execution.parallel.enabled = true
  2. junit.jupiter.execution.parallel.mode.default = same_thread
  3. junit.jupiter.execution.parallel.mode.classes.default = concurrent

The opposite combination will run all methods within one class in parallel, but top-level classes will run sequentially:

Configuration parameters to execute top-level classes sequentially but their methods in parallel

  1. junit.jupiter.execution.parallel.enabled = true
  2. junit.jupiter.execution.parallel.mode.default = concurrent
  3. junit.jupiter.execution.parallel.mode.classes.default = same_thread

The following diagram illustrates how the execution of two top-level test classes A and B with two test methods per class behaves for all four combinations of junit.jupiter.execution.parallel.mode.default and junit.jupiter.execution.parallel.mode.classes.default (see labels in first column).

writing tests execution mode

Default execution mode configuration combinations

If the junit.jupiter.execution.parallel.mode.classes.default configuration parameter is not explicitly set, the value for junit.jupiter.execution.parallel.mode.default will be used instead.

2.19.1. Configuration

Properties such as the desired parallelism and the maximum pool size can be configured using a [ParallelExecutionConfigurationStrategy](https://junit.org/junit5/docs/current/api/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.html). The JUnit Platform provides two implementations out of the box: dynamic and fixed. Alternatively, you may implement a custom strategy.

To select a strategy, set the junit.jupiter.execution.parallel.config.strategy configuration parameter to one of the following options.

dynamic

Computes the desired parallelism based on the number of available processors/cores multiplied by the junit.jupiter.execution.parallel.config.dynamic.factor configuration parameter (defaults to 1).

fixed

Uses the mandatory junit.jupiter.execution.parallel.config.fixed.parallelism configuration parameter as the desired parallelism.

custom

Allows you to specify a custom [ParallelExecutionConfigurationStrategy](https://junit.org/junit5/docs/current/api/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.html) implementation via the mandatory junit.jupiter.execution.parallel.config.custom.class configuration parameter to determine the desired configuration.

If no configuration strategy is set, JUnit Jupiter uses the dynamic configuration strategy with a factor of 1. Consequently, the desired parallelism will be equal to the number of available processors/cores.

Parallelism does not imply maximum number of concurrent threads
JUnit Jupiter does not guarantee that the number of concurrently executing tests will not exceed the configured parallelism. For example, when using one of the synchronization mechanisms described in the next section, the ForkJoinPool that is used behind the scenes may spawn additional threads to ensure execution continues with sufficient parallelism. Thus, if you require such guarantees in a test class, please use your own means of controlling concurrency.

2.19.2. Synchronization

In addition to controlling the execution mode using the [@Execution](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Execution.html) annotation, JUnit Jupiter provides another annotation-based declarative synchronization mechanism. The [@ResourceLock](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLock.html) annotation allows you to declare that a test class or method uses a specific shared resource that requires synchronized access to ensure reliable test execution. The shared resource is identified by a unique name which is a String. The name can be user-defined or one of the predefined constants in [Resources](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Resources.html): SYSTEM_PROPERTIES, SYSTEM_OUT, SYSTEM_ERR, LOCALE, or TIME_ZONE.

If the tests in the following example were run in parallel without the use of @ResourceLock, they would be flaky. Sometimes they would pass, and at other times they would fail due to the inherent race condition of writing and then reading the same JVM System Property.

When access to shared resources is declared using the [@ResourceLock](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLock.html) annotation, the JUnit Jupiter engine uses this information to ensure that no conflicting tests are run in parallel.

Running tests in isolation

If most of your test classes can be run in parallel without any synchronization but you have some test classes that need to run in isolation, you can mark the latter with the @Isolated annotation. Tests in such classes are executed sequentially without any other tests running at the same time.

In addition to the String that uniquely identifies the shared resource, you may specify an access mode. Two tests that require READ access to a shared resource may run in parallel with each other but not while any other test that requires READ_WRITE access to the same shared resource is running.

  1. @Execution(CONCURRENT)
  2. class SharedResourcesDemo {
  3. private Properties backup;
  4. @BeforeEach
  5. void backup() {
  6. backup = new Properties();
  7. backup.putAll(System.getProperties());
  8. }
  9. @AfterEach
  10. void restore() {
  11. System.setProperties(backup);
  12. }
  13. @Test
  14. @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ)
  15. void customPropertyIsNotSetByDefault() {
  16. assertNull(System.getProperty("my.prop"));
  17. }
  18. @Test
  19. @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE)
  20. void canSetCustomPropertyToApple() {
  21. System.setProperty("my.prop", "apple");
  22. assertEquals("apple", System.getProperty("my.prop"));
  23. }
  24. @Test
  25. @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE)
  26. void canSetCustomPropertyToBanana() {
  27. System.setProperty("my.prop", "banana");
  28. assertEquals("banana", System.getProperty("my.prop"));
  29. }
  30. }