JUnit5學習之八:綜合進階(終篇)
阿新 • • 發佈:2021-03-01
### 歡迎訪問我的GitHub
[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos)
內容:所有原創文章分類彙總及配套原始碼,涉及Java、Docker、Kubernetes、DevOPS等;
### 關於《JUnit5學習》系列
《JUnit5學習》系列旨在通過實戰提升SpringBoot環境下的單元測試技能,一共八篇文章,連結如下:
1. [基本操作](https://blog.csdn.net/boling_cavalry/article/details/108810587)
2. [Assumptions類](https://blog.csdn.net/boling_cavalry/article/details/108861185)
3. [Assertions類](https://blog.csdn.net/boling_cavalry/article/details/108899437)
4. [按條件執行](https://blog.csdn.net/boling_cavalry/article/details/108909107)
5. [標籤(Tag)和自定義註解](https://blog.csdn.net/boling_cavalry/article/details/108914091)
6. [引數化測試(Parameterized Tests)基礎](https://blog.csdn.net/boling_cavalry/article/details/108930987)
7. [引數化測試(Parameterized Tests)進階](https://blog.csdn.net/boling_cavalry/article/details/108942301)
8. [綜合進階(終篇)](https://blog.csdn.net/boling_cavalry/article/details/108952500)
### 本篇概覽
- 本文是《JUnit5學習》系列的終篇,將JUnit5提供的一些高階特性以實戰的形式展現出來;
- JUnit5的特性非常多,《JUnit5學習》系列也只是將常用部分寫出來,未能覆蓋全部;
- 本文由以下章節組成:
1. 版本設定
2. 測試方法展現名稱生成器
3. 重複測試
4. 巢狀
5. 動態測試(Dynamic Tests)
6. 多執行緒併發執行測試方法
### 原始碼下載
1. 如果您不想編碼,可以在GitHub下載所有原始碼,地址和連結資訊如下表所示:
| 名稱 | 連結 | 備註|
| :-------- | :----| :----|
| 專案主頁| https://github.com/zq2599/blog_demos | 該專案在GitHub上的主頁 |
| git倉庫地址(https)| https://github.com/zq2599/blog_demos.git | 該專案原始碼的倉庫地址,https協議 |
| git倉庫地址(ssh)| [email protected]:zq2599/blog_demos.git | 該專案原始碼的倉庫地址,ssh協議 |
2. 這個git專案中有多個資料夾,本章的應用在junitpractice資料夾下,如下圖紅框所示:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202103/485422-20210301084602934-127204403.jpg)
3. junitpractice是父子結構的工程,本篇的程式碼在advanced子工程中,如下圖:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202103/485422-20210301084606955-1574233116.jpg)
### 版本設定
- 《JUnit5學習》系列的程式碼都在用SpringBoot:2.3.4.RELEASE框架,間接依賴的JUnit版本是5.6.2;
- 本文有兩個特性要求JUnit版本達到5.7或者更高,它們是測試方法展現名稱生成器和動態生成測試方法;
- 對於使用SpringBoot:2.3.4.RELEASE框架的工程,如果要指定JUnit版本,需要做以下三步操作:
1. dependencyManagement節點新增junit-bom,並指定版本號:
```xml
```
2. 排除spring-boot-starter-test和junit-jupiter的間接依賴關係:
```xml
```
3. 新增junit-jupiter依賴,此時會使用dependencyManagement中指定的版本號:
```xml
```
4. 如下圖,重新整理可見已經用上了5.7.0版本:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202103/485422-20210301084611138-1949387606.jpg)
- 版本問題解決了,接下來正式進入進階實戰;
### 測試方法展現名稱生成器(Display Name Generators)
1. 把Display Name Generators翻譯成測試方法展現名稱生成器,可能重新整理了讀者們對本文作者英文水平的認知,請您多包含...
2. 先回顧一下如何指定測試方法的展現名稱,如果測試方法使用了@DisplayName,在展示單元測試執行結果時,就會顯示@DisplayName指定的字串,如下圖所示:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202103/485422-20210301084613170-178275254.jpg)
3. 除了用@DisplayName指定展示名稱,JUnit5還提供了一種自動生成展示名稱的功能:@DisplayNameGeneration,來看看它是如何生成展示名稱的;
4. 演示程式碼如下所示,當@DisplayNameGeneration的value設定為ReplaceUnderscores時,會把方法名的所有下劃線替換為空格:
```java
package com.bolingcavalry.advanced.service.impl;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
public class ReplaceUnderscoresTest {
@Test
void if_it_is_zero() {
}
}
```
5. 執行結果如下圖,方法if_it_is_zero展示出的名字為if it is zero:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202103/485422-20210301084614328-1893041996.jpg)
6. 在上述替換方式的基礎上,JUnit5還提供了另一種生成展示名稱的方法:測試類名+連線符+測試方法名,並且類名和方法名的下劃線都會被替換成空格,演示程式碼如下,使用了註解@IndicativeSentencesGeneration,其separator屬性就是類名和方法名之間的連線符:
```java
package com.bolingcavalry.advanced.service.impl;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.IndicativeSentencesGeneration;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@IndicativeSentencesGeneration(separator = ",測試方法:", generator = DisplayNameGenerator.ReplaceUnderscores.class)
public class IndicativeSentences_Test {
@Test
void if_it_is_one_of_the_following_years() {
}
}
```
7. 執行結果如下:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202103/485422-20210301084617720-996503409.jpg)
### 重複測試(Repeated Tests)
1. 重複測試就是指定某個測試方法反覆執行多次,演示程式碼如下,可見@Test已被@RepeatedTest(5)取代,數字5表示重複執行5次:
```java
@Order(1)
@DisplayName("重複測試")
@RepeatedTest(5)
void repeatTest(TestInfo testInfo) {
log.info("測試方法 [{}]", testInfo.getTestMethod().get().getName());
}
```
2. 執行結果如下圖:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202103/485422-20210301084627369-860523040.jpg)
3. 在測試方法執行時,如果想了解當前是第幾次執行,以及總共有多少次,只要給測試方法增加RepetitionInfo型別的入參即可,演示程式碼如下,可見RepetitionInfo提供的API可以得到總數和當前次數:
```java
@Order(2)
@DisplayName("重複測試,從入參獲取執行情況")
@RepeatedTest(5)
void repeatWithParamTest(TestInfo testInfo, RepetitionInfo repetitionInfo) {
log.info("測試方法 [{}],當前第[{}]次,共[{}]次",
testInfo.getTestMethod().get().getName(),
repetitionInfo.getCurrentRepetition(),
repetitionInfo.getTotalRepetitions());
}
```
4. 上述程式碼執行結果如下:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202103/485422-20210301084635404-64538414.jpg)
5. 在上圖的左下角可見,重複執行的結果被展示為"repetition X of X"這樣的內容,其實這部分資訊是可以定製的,就是RepeatedTest註解的name屬性,演示程式碼如下,可見currentRepetition和totalRepetitions是佔位符,在真正展示的時候會被分別替換成當前值和總次數:
```java
@Order(3)
@DisplayName("重複測試,使用定製名稱")
@RepeatedTest(value = 5, name="完成度:{currentRepetition}/{totalRepetitions}")
void repeatWithCustomDisplayNameTest(TestInfo testInfo, RepetitionInfo repetitionInfo) {
log.info("測試方法 [{}],當前第[{}]次,共[{}]次",
testInfo.getTestMethod().get().getName(),
repetitionInfo.getCurrentRepetition(),
repetitionInfo.getTotalRepetitions());
}
```
6. 上述程式碼執行結果如下:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202103/485422-20210301084642205-1423999425.jpg)
### 巢狀測試(Nested Tests)
1. 如果一個測試類中有很多測試方法(如增刪改查,每種操作都有多個測試方法),那麼不論是管理還是結果展現都會顯得比較複雜,此時巢狀測試(Nested Tests)就派上用場了;
2. 巢狀測試(Nested Tests)功能就是在測試類中建立一些內部類,以增刪改查為例,將所有測試查詢的方法放入一個內部類,將所有測試刪除的方法放入另一個內部類,再給每個內部類增加@Nested註解,這樣就會以內部類為單位執行測試和展現結果,如下圖所示:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202103/485422-20210301084644258-287878639.jpg)
3. 巢狀測試的演示程式碼如下:
```java
package com.bolingcavalry.advanced.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@Slf4j
@DisplayName("巢狀測試演示")
public class NestedTest {
@Nested
@DisplayName("查詢服務相關的測試")
class FindService {
@Test
void findByIdTest() {}
@Test
void findByNameTest() {}
}
@Nested
@DisplayName("刪除服務相關的測試")
class DeleteService {
@Test
void deleteByIdTest() {}
@Test
void deleteByNameTest() {}
}
}
```
4. 上述程式碼執行結果如下,可見從程式碼管理再到執行和結果展示,都被分組管理了:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202103/485422-20210301084647406-1933594105.jpg)
### 動態測試(Dynamic Tests)
1. 之前咱們寫的測試方法,主要是用@Test修飾,這些方法的特點就是在編譯階段就已經明確了,在執行階段也已經固定;
2. JUnit5推出了另一種型別的測試方法:動態測試(Dynamic Tests),首先,測試方法是可以在執行期間被生產出來的,生產它們的地方,就是被@TestFactory修飾的方法,等到測試方法被生產出來後再像傳統的測試方法那樣被執行和結果展示;
3. 下面是演示程式碼,testFactoryTest方法被@TestFactory修飾,返回值是Iterable型別,裡面是多個DynamicTest例項,每個DynamicTest例項代表一個測試方法,因此,整個DynamicDemoTest類中有多少個測試方法,在編譯階段是不能確定的,只有在執行階段執行了testFactoryTest方法後,才能根據返回值確定下來:
```java
package com.bolingcavalry.advanced.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
@SpringBootTest
@Slf4j
class DynamicDemoTest {
@TestFactory
Iterable