Running JUnit Tests in Parallel with Maven – 用Maven并行运行JUnit测试

最后修改: 2018年 8月 13日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

Although executing tests serially works just fine most of the time, we may want to parallelize them to speed things up.

尽管在大多数情况下,连续执行测试的效果很好,但我们可能想把它们并行化,以加快事情的进展。

In this tutorial, we’ll cover how to parallelize tests using JUnit and Maven’s Surefire Plugin. First, we’ll run all tests in a single JVM process, then we’ll try it with a multi-module project.

在本教程中,我们将介绍如何使用JUnit和Maven的Surefire插件进行并行测试。首先,我们将在单个JVM进程中运行所有测试,然后在一个多模块项目中尝试。

2. Maven Dependencies

2.Maven的依赖性

Let’s begin by importing the required dependencies. We’ll need to use JUnit 4.7 or later along with Surefire 2.16 or later:

让我们首先导入所需的依赖项。我们需要使用JUnit 4.7或更高版本,以及Surefire 2.16或更高版本:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.21.0</version>
</plugin>

In a nutshell, Surefire provides two ways of executing tests in parallel:

简而言之,Surefire提供了两种并行执行测试的方法。

  • Multithreading inside a single JVM process
  • Forking multiple JVM processes

3. Running Parallel Tests

3.运行平行测试

To run a test in parallel we should use a test runner that extends org.junit.runners.ParentRunner.

为了并行运行一个测试,我们应该使用一个扩展了org.junit.runners.ParentRunner的测试运行器。

However, even tests that don’t declare an explicit test runner work, as the default runner extends this class.

然而,即使没有声明明确的测试运行器的测试也能工作,因为默认的运行器扩展了这个类。

Next, to demonstrate parallel test execution, we’ll use a test suite with two test classes each having a few methods. In fact, any standard implementation of a JUnit test suite would do.

接下来,为了演示并行测试的执行,我们将使用一个有两个测试类的测试套件,每个测试类有几个方法。事实上,任何JUnit测试套件的标准实现都可以。

3.1. Using Parallel Parameter

3.1.使用并行参数

First, let’s enable parallel behavior in Surefire using the parallel parameter. It states the level of granularity at which we’d like to apply parallelism.

首先,让我们在Surefire中使用parallel参数来启用并行行为。它说明了我们希望应用并行的粒度水平。

The possible values are:

可能的值是。

  • methods – runs test methods in separate threads
  • classes – runs test classes in separate threads
  • classesAndMethods – runs classes and methods in separate threads
  • suites – runs suites in parallel
  • suitesAndClasses – runs suites and classes in separate threads
  • suitesAndMethods – creates separate threads for classes and for methods
  • all – runs suites, classes as well as methods in separate threads

In our example, we use all:

在我们的例子中,我们使用all

<configuration>
    <parallel>all</parallel>
</configuration>

Second, let’s define the total number of threads we want Surefire to create. We can do that in two ways:

第二,让我们定义一下我们希望Surefire创建的线程总数。我们可以通过两种方式来做到这一点。

Using threadCount which defines the maximum number of threads Surefire will create:

使用threadCount,它定义了Surefire将创建的最大线程数。

<threadCount>10</threadCount>

Or using useUnlimitedThreads parameter where one thread is created per CPU core:

或者使用useUnlimitedThreads参数,每个CPU核创建一个线程。

<useUnlimitedThreads>true</useUnlimitedThreads>

By default, threadCount is per CPU core. We can use the parameter perCoreThreadCount to enable or disable this behavior:

默认情况下,threadCount是按CPU核计算。我们可以使用参数perCoreThreadCount来启用或禁用这一行为。

<perCoreThreadCount>true</perCoreThreadCount>

3.2. Using Thread-Count Limitations

3.2.使用线程数的限制

Now, let’s say we want to define the number of threads to create at the method, class, and suite level. We can do this with the threadCountMethods, threadCountClasses and threadCountSuites parameters.

现在,假设我们想在方法、类和套件层面上定义创建线程的数量。我们可以通过threadCountMethodsthreadCountClassesthreadCountSuites参数做到这一点。

Let’s combine these parameters with threadCount from the previous configuration: 

让我们将这些参数与之前配置的threadCount结合起来:

<threadCountSuites>2</threadCountSuites>
<threadCountClasses>2</threadCountClasses>
<threadCountMethods>6</threadCountMethods>

Since we used all in parallel, we’ve defined the thread counts for methods, suites, and classes. However, it isn’t mandatory to define the leaf parameter. Surefire deduces the number of threads to use in case leaf parameters are omitted.

由于我们在parallel中使用了all我们已经定义了方法、套件和类的线程数。然而,定义叶子参数并不是强制性的。在省略了叶子参数的情况下,Surefire会推断出要使用的线程数。

For example, if threadCountMethods is omitted, then we just need to make sure threadCount > threadCountClasses threadCountSuites.

例如,如果threadCountMethods被省略,那么我们只需要确保threadCount>threadCountClasses+threadCountSuites.

Sometimes we may want to limit the number of threads created for classes or suites or methods even while we’re using an unlimited number of threads.

有时我们可能想限制为类或套件或方法创建的线程数量,即使我们在使用无限数量的线程。

We can apply thread-count limitations in such cases as well:

在这种情况下,我们也可以应用线程数限制。

<useUnlimitedThreads>true</useUnlimitedThreads>
<threadCountClasses>2</threadCountClasses>

3.3. Setting Timeouts

3.3.设置超时

Sometimes we may need to ensure that test execution is time-bounded.

有时我们可能需要确保测试执行是有时间限制的。

To do that we can use the parallelTestTimeoutForcedInSeconds parameter. This will interrupt currently running threads and will not execute any of the queued threads after the timeout has elapsed:

为了做到这一点,我们可以使用parallelTestTimeoutForcedInSeconds参数。这将中断当前正在运行的线程,并且在超时过后不会执行任何排队的线程。

<parallelTestTimeoutForcedInSeconds>5</parallelTestTimeoutForcedInSeconds>

Another option is to use parallelTestTimeoutInSeconds.

另一个选择是使用parallelTestTimeoutInSeconds.

In this case, only the queued threads will be stopped from executing:

在这种情况下,只有排队的线程会被停止执行。

<parallelTestTimeoutInSeconds>3.5</parallelTestTimeoutInSeconds>

Nevertheless, with both options, the tests will end with an error message when the timeout has elapsed.

然而,在这两个选项中,当超时结束时,测试将以错误信息结束。

3.4. Caveats

3.4.注意事项

Surefire calls static methods annotated with @Parameters, @BeforeClass, and @AfterClass in the parent thread. Thus make sure to check for potential memory inconsistencies or race conditions before running tests in parallel.

Surefire在父线程中调用带有@Parameters, @BeforeClass, 和@AfterClass注释的静态方法。因此,在并行运行测试之前,请确保检查潜在的内存不一致或竞赛条件。

Also, tests that mutate shared state are definitely not good candidates for running in parallel.

另外,突变共享状态的测试绝对不是并行运行的好选择。

4. Test Execution in Multi-Module Maven Projects

4.多模块Maven项目的测试执行

Till now, we’ve focused on running tests in parallel within a Maven module.

到目前为止,我们的重点是在Maven模块中并行运行测试。

But let’s say we have multiple modules in a Maven project. Since these modules are built sequentially, the tests for each module are also executed sequentially.

但假设我们在一个Maven项目中拥有多个模块。由于这些模块是按顺序构建的,每个模块的测试也是按顺序执行的。

We can change this default behavior by using Maven’s -T parameter which builds modules in parallel. This can be done in two ways.

我们可以通过使用Maven的-T参数来改变这种默认行为,该参数可以并行构建模块。这可以通过两种方式实现。

We can either specify the exact number of threads to use while building the project:

我们可以在构建项目时指定要使用的线程的确切数量。

mvn -T 4 surefire:test

Or use the portable version and specify the number of threads to create per CPU core:

或者使用可移植版本,并指定每个CPU核要创建的线程数。

mvn -T 1C surefire:test

Either way, we can speed up tests as well as build execution times.

无论哪种方式,我们都可以加快测试以及构建执行时间。

5. Forking JVMs

5.分叉JVM

With the parallel test execution via the parallel option, concurrency happens inside the JVM process using threads.

通过parallel选项进行的并行测试执行,使用线程在JVM进程中发生并发

Since threads are sharing the same memory space, this can be efficient in terms of memory and speed. However, we may encounter unexpected race conditions or other subtle concurrency-related test failures. As it turns out, sharing the same memory space can be both a blessing and a curse.

由于线程共享相同的内存空间,这在内存和速度方面都是高效的。然而,我们可能会遇到意外的竞赛条件或其他微妙的并发相关的测试失败。事实证明,共享相同的内存空间既是一种祝福也是一种诅咒。

To prevent thread-level concurrency issues, Surefire provides another parallel test execution mode: forking and process-level concurrency. The idea of forked processes is actually quite simple. Instead of spawning multiple threads and distributing the test methods between them, surefire creates new processes and does the same distribution.

为了防止线程级并发问题,Surefire提供了另一种并行测试执行模式。分叉和进程级并发。分叉进程的想法其实很简单。与其催生多个线程并在它们之间分配测试方法,surefire创建新的进程并做同样的分配。

Since there’s no shared memory between different processes, we won’t suffer from those subtle concurrency bugs. Of course, this comes at the expense of more memory usage and a little less speed.

由于不同进程之间没有共享内存,我们不会受到那些微妙的并发错误的影响。当然,这是以更多的内存使用和更低的速度为代价的。

Anyway, in order to enable forking, we just have to use the forkCount property and set it to any positive value:

总之,为了启用分叉,我们只需使用forkCount 属性并将其设置为任何正值:

<forkCount>3</forkCount>

Here, surefire will create at most three forks from the JVM and run the tests in them. The default value for forkCount is one, which means that maven-surefire-plugin creates one new JVM process to execute all tests in one Maven module.

这里,surefire最多可以从JVM中创建三个分叉,并在其中运行测试。forkCount的默认值是1,这意味着maven-surefire-plugin会创建一个新的JVM进程来执行一个Maven模块的所有测试。

The forkCount property supports the same syntax as -T. That is, if we append the to the value, that value will be multiplied with the number of available CPU cores in our system. For instance:

forkCount属性支持与T相同的语法。也就是说,如果我们将C附加到该值上,该值将与我们系统中可用的CPU核心数量相乘。比如说。

<forkCount>2.5C</forkCount>

Then in a two-core machine, Surefire can create at most five forks for parallel test execution.

那么在双核机器中,Surefire最多可以创建五个分叉,用于并行测试执行。

By default, Surefire will reuse the created forks for other tests. However, if we set the reuseForks property to false, it’ll destroy each fork after running one test class.

默认情况下,Surefire将为其他测试重复使用创建的分叉。然而,如果我们将reuseForks属性设置为false,它将在运行一个测试类后销毁每个分叉。

Also, in order to disable the forking, we can set the forkCount to zero.

另外,为了禁止分叉,我们可以将forkCount设置为零。

6. Conclusion

6.结论

To sum up, we started off by enabling multi-threaded behavior and defining the degree of parallelism using the parallel parameter. Subsequently, we applied limitations on the number of threads Surefire should create. Later, we set timeout parameters to control test execution times.

总而言之,我们首先启用了多线程行为,并使用parallel参数定义了并行程度。随后,我们对Surefire应该创建的线程数量进行了限制。后来,我们设置了超时参数来控制测试执行时间。

Finally, we looked at how we can reduce build execution times and therefore test execution times in multi-module Maven projects.

最后,我们研究了如何在多模块Maven项目中减少构建执行时间,从而减少测试执行时间。

As always, the code presented here is available on GitHub.

一如既往,这里介绍的代码是在GitHub上提供的