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
junit.jupiter.execution.parallel.enabled = true
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
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = same_thread
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
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
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).
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 |
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.
@Execution(CONCURRENT)
class SharedResourcesDemo {
private Properties backup;
@BeforeEach
void backup() {
backup = new Properties();
backup.putAll(System.getProperties());
}
@AfterEach
void restore() {
System.setProperties(backup);
}
@Test
@ResourceLock(value = SYSTEM_PROPERTIES, mode = READ)
void customPropertyIsNotSetByDefault() {
assertNull(System.getProperty("my.prop"));
}
@Test
@ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE)
void canSetCustomPropertyToApple() {
System.setProperty("my.prop", "apple");
assertEquals("apple", System.getProperty("my.prop"));
}
@Test
@ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE)
void canSetCustomPropertyToBanana() {
System.setProperty("my.prop", "banana");
assertEquals("banana", System.getProperty("my.prop"));
}
}