JUnit 5如何執行並行測試
- junit
一、簡介
在本文中,我們將介紹如何使用 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
允許我們指定共享哪個資源以及我們想要使用的鎖類型(默認為ResourceAccessMode.READ_WRITE
) 。使用當前設置,JUnit 引擎將檢測到我們的測試都使用共享資源並按順序執行它們:
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
六,結論
在本文中,我們首先介紹瞭如何配置並行執行。接下來,有哪些可用的並行策略以及如何配置多個線程。之後,我們介紹了不同的配置如何影響測試執行。最後,我們介紹了共享資源的同步。