一、简介
在本文中,我们将介绍如何使用JUnit 5 执行并行单元测试。首先,我们将介绍开始使用此功能的基本配置和最低要求。接下来,我们将展示针对不同情况的代码示例,最后我们将讨论共享资源的同步。
并行测试执行是一项实验性功能,自5.3 版起可选择加入。
2. 配置
首先,我们通过在提到的文件中添加以下行来启用并行化功能:src/test/resources
junit-platform.properties
junit.jupiter.execution.parallel.enabled = true
让我们通过运行一些测试来检查我们的配置。首先,我们将创建FirstParallelUnitTest
public class FirstParallelUnitTest{ @Test public void first() throws Exception{ System.out.println("FirstParallelUnitTest first() start => " + Thread.currentThread().getName()); Thread.sleep(500); System.out.println("FirstParallelUnitTest first() end => " + Thread.currentThread().getName()); } @Test public void second() throws Exception{ System.out.println("FirstParallelUnitTest second() start => " + Thread.currentThread().getName()); Thread.sleep(500); System.out.println("FirstParallelUnitTest second() end => " + Thread.currentThread().getName()); } }
当我们运行测试时,我们在控制台中得到以下输出:
FirstParallelUnitTest second() start => ForkJoinPool-1-worker-19 FirstParallelUnitTest second() end => ForkJoinPool-1-worker-19 FirstParallelUnitTest first() start => ForkJoinPool-1-worker-19 FirstParallelUnitTest first() end => ForkJoinPool-1-worker-19
在这个输出中,我们可以注意到两件事。首先,我们的测试按顺序运行。其次,我们使用ForkJoin 线程池。通过启用并行执行,JUnit 引擎开始使用ForkJoin 线程池。
接下来,我们需要添加一个配置来利用这个线程池。我们需要选择一个并行化策略。JUnit 提供了两个实现(dynamic
fixed
a custom
动态策略根据处理器/内核数乘以因子参数(默认为1)来确定线程数:
junit.jupiter.execution.parallel.config.dynamic.factor
另一方面,固定策略依赖于由以下指定的预定义线程数:
junit.jupiter.execution.parallel.config.fixed.parallelism
要使用自定义策略,我们需要首先通过实现ParallelExecutionConfigurationStrategy
3. 在类内测试并行化
我们已经启用了并行执行并选择了一个策略。现在是在同一个类中并行执行测试的时候了。有两种方法可以配置它。一种是使用@Execution(ExecutionMode.CONCURRENT)
junit.jupiter.execution.parallel.mode.default = concurrent
在我们选择如何配置它并运行我们的FirstParallelUnitTest
FirstParallelUnitTest second() start => ForkJoinPool-1-worker-5 FirstParallelUnitTest first() start => ForkJoinPool-1-worker-19 FirstParallelUnitTest second() end => ForkJoinPool-1-worker-5 FirstParallelUnitTest first() end => ForkJoinPool-1-worker-19
从输出中,我们可以看到两个测试同时在两个不同的线程中启动。请注意,输出可以从一次运行更改为另一次运行。使用ForkJoin 线程池时,这是预期的。
还有一个选项可以在同一线程中FirstParallelUnitTest
4. 测试模块内的并行化
在我们引入一个新属性之前,我们将创建SecondParallelUnitTest
FirstParallelUnitTest:
public class SecondParallelUnitTest{ @Test public void first() throws Exception{ System.out.println("SecondParallelUnitTest first() start => " + Thread.currentThread().getName()); Thread.sleep(500); System.out.println("SecondParallelUnitTest first() end => " + Thread.currentThread().getName()); } @Test public void second() throws Exception{ System.out.println("SecondParallelUnitTest second() start => " + Thread.currentThread().getName()); Thread.sleep(500); System.out.println("SecondParallelUnitTest second() end => " + Thread.currentThread().getName()); } }
在我们在同一批中运行我们的测试之前,我们需要设置属性:
junit.jupiter.execution.parallel.mode.classes.default = concurrent
当我们运行这两个测试类时,我们得到以下输出:
SecondParallelUnitTest second() start => ForkJoinPool-1-worker-23 FirstParallelUnitTest first() start => ForkJoinPool-1-worker-19 FirstParallelUnitTest second() start => ForkJoinPool-1-worker-9 SecondParallelUnitTest first() start => ForkJoinPool-1-worker-5 FirstParallelUnitTest first() end => ForkJoinPool-1-worker-19 SecondParallelUnitTest first() end => ForkJoinPool-1-worker-5 FirstParallelUnitTest second() end => ForkJoinPool-1-worker-9 SecondParallelUnitTest second() end => ForkJoinPool-1-worker-23
从输出中,我们可以看到所有四个测试在不同的线程中并行运行。
结合我们在本节和上一节中提到的两个属性及其值(same_thread and concurrent
(
same_thread, same_thread
(
same_thread, concurrent
(
concurrent, same_thread
(
concurrent, concurrent
5. 同步
在理想情况下,我们所有的单元测试都是独立和隔离的。然而,有时这很难实现,因为它们依赖于共享资源。然后,在并行运行测试时,我们需要同步测试中的公共资源。注解的形式为我们提供了这样的机制。@ResourceLock
同样,和以前一样,让我们创建ParallelResourceLockUnitTest
public class ParallelResourceLockUnitTest{ private List<String> resources; @BeforeEach void before() { resources = new ArrayList<>(); resources.add("test"); } @AfterEach void after() { resources.clear(); } @Test @ResourceLock(value = "resources") public void first() throws Exception { System.out.println("ParallelResourceLockUnitTest first() start => " + Thread.currentThread().getName()); resources.add("first"); System.out.println(resources); Thread.sleep(500); System.out.println("ParallelResourceLockUnitTest first() end => " + Thread.currentThread().getName()); } @Test @ResourceLock(value = "resources") public void second() throws Exception { System.out.println("ParallelResourceLockUnitTest second() start => " + Thread.currentThread().getName()); resources.add("second"); System.out.println(resources); Thread.sleep(500); System.out.println("ParallelResourceLockUnitTest second() end => " + Thread.currentThread().getName()); } }
@ResourceLock
允许我们指定共享哪个资源以及我们想要使用的锁类型(默认为 使用当前设置,JUnit 引擎将检测到我们的测试都使用共享资源并按顺序执行它们:ResourceAccessMode.READ_WRITE
ParallelResourceLockUnitTest second() start => ForkJoinPool-1-worker-5 [test, second] ParallelResourceLockUnitTest second() end => ForkJoinPool-1-worker-5 ParallelResourceLockUnitTest first() start => ForkJoinPool-1-worker-19 [test, first] ParallelResourceLockUnitTest first() end => ForkJoinPool-1-worker-19
六,结论
在本文中,我们首先介绍了如何配置并行执行。接下来,有哪些可用的并行策略以及如何配置多个线程。之后,我们介绍了不同的配置如何影响测试执行。最后,我们介绍了共享资源的同步。
0 评论