JUnit 5 將撰寫測試所使用的 API,與探索及執行測試的基礎設施分開。這項分離讓 Maven、IDE 與自訂啟動器能透過單一介面執行數種測試模型。
範例以 JUnit 5.13.4 為目標。JUnit 6 保留相同架構,但必須維持在 JUnit 5 的專案應使用版本化的 JUnit 5 文件。
Platform、Jupiter 與 Vintage #
JUnit 5 由三個元件組成:
| 元件 | 責任 |
|---|---|
| JUnit Platform | 探索測試並啟動已註冊的測試引擎。Maven Surefire 與 IDE 在此層級整合。 |
| JUnit Jupiter | 定義 JUnit 5 程式設計模型、擴充 API 與測試引擎。 |
| JUnit Vintage | 在遷移期間於 Platform 上執行 JUnit 3 與 JUnit 4 測試。 |
Jupiter 測試從 org.junit.jupiter.api 匯入 @Test 與判斷。Surefire 要求 Platform 探索測試,接著由 junit-jupiter-engine 執行探索到的方法。應用程式程式碼不依賴任何 JUnit 元件,因為測試相依性使用 Maven 的 test 範圍。
最小 Maven 專案 #
以下配置將正式類別與測試類別分開:
junit-hello/
├── pom.xml
└── src
├── main/java/com/example/Greeter.java
└── test/java/com/example/GreeterTest.java最小且可維護的 POM 使用 org.junit.jupiter:junit-jupiter。該成品彙總 Jupiter API、參數化測試支援與執行階段引擎。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>junit-hello</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<junit.version>5.13.4</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
</plugin>
</plugins>
</build>
</project>package com.example;
public final class Greeter {
public String greet(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("name must not be blank");
}
return "Hello, " + name;
}
}package com.example;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
class GreeterTest {
private final Greeter greeter = new Greeter();
@Test
void greetsByName() {
assertEquals("Hello, Ada", greeter.greet("Ada"));
}
@Test
void rejectsBlankNames() {
var error = assertThrows(
IllegalArgumentException.class,
() -> greeter.greet(" "));
assertEquals("name must not be blank", error.getMessage());
}
}透過 Maven 執行測試:
mvn testJupiter 測試類別與方法可以使用套件私有可見性。測試方法不得是 private 或 static,但生命週期與擴充規則允許在特定位置使用 static。
選擇 JUnit 成品 #
只有在需要額外能力時才加入另一個成品:
| 成品 | 加入時機 |
|---|---|
junit-jupiter | 應用程式測試套件優先使用彙總相依性。它已包含 API、參數與引擎。 |
junit-jupiter-api | 在分開管理執行階段引擎時,針對 API 編譯測試或擴充。 |
junit-jupiter-engine | 不使用 junit-jupiter 而採用細粒度相依性時,執行 Jupiter 測試。使用測試執行階段範圍。 |
junit-jupiter-params | 對細粒度設定加入 @ParameterizedTest 支援。彙總相依性已包含它。 |
junit-vintage-engine | 透過 Platform 執行未變更的 JUnit 3 或 JUnit 4 測試。 |
junit-jupiter-migrationsupport | 轉換至 Jupiter 時,暫時調整選定的 JUnit 4 規則與 @Ignore。 |
junit-platform-suite | 使用 Platform 套件註解宣告套件。 |
junit-platform-launcher | 以程式探索並執行測試,或實作測試工具。應用程式測試套件不需要直接使用它。 |
junit-platform-console-standalone | 從單一可執行 JAR 在 Maven 或 IDE 外執行測試。將 JAR 保留在專案 POM 外。 |
當建置宣告多個 JUnit 成品時,匯入 JUnit BOM,因為 BOM 會讓 Platform、Jupiter 與 Vintage 版本保持相容:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${junit.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>BOM 涵蓋的成品宣告可以省略 <version>。
在 3.0 之前的 Surefire 發行版本中,明確的 junit-platform-launcher 相依性是必要的。目前 Surefire 3.x 發行版會在測試執行階段類別路徑上,讓啟動器與 Platform 對齊,因此對一般測試套件加入啟動器只會增加另一個需要管理的版本,不會增加測試能力。
Jupiter 程式設計模型 #
Jupiter 為常見測試結構提供聚焦的註解:
@BeforeEach與@AfterEach建立每次測試的固定裝置。@BeforeAll與@AfterAll建立類別層級的固定裝置。@ParameterizedTest使用@ValueSource、@CsvSource、@MethodSource或其他引數來源的資料,執行一個測試本體。@Nested在保留生命週期語意的同時,將相關案例分組。@Tag將測試分類,以便在建置時納入或排除。@ExtendWith為參數解析、回呼、條件執行及其他橫切行為註冊擴充。
測試應斷言可觀察結果,而不是執行順序。Jupiter 不保證從方法名稱可明確推知順序,而有順序的測試會共享狀態,使失敗取決於先前案例。
不變更地執行 JUnit 4 測試 #
Vintage 提供風險最低的第一個遷移步驟,因為 Jupiter 與 JUnit 4 使用不同 Java 套件,且可以共存於一個模組:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>Surefire 接著透過 Platform 探索 Jupiter 與 JUnit 4 測試。只要仍有舊測試,就保留 Vintage,因為成功的 Vintage 執行不會測試 Jupiter 的程式設計或擴充模型。
轉換 JUnit 4 測試 #
常見的來源對應如下:
| JUnit 4 | Jupiter |
|---|---|
@Before / @After | @BeforeEach / @AfterEach |
@BeforeClass / @AfterClass | @BeforeAll / @AfterAll |
@Ignore | @Disabled |
@Category | @Tag |
@RunWith | 一個或多個 @ExtendWith 擴充 |
@Rule / @ClassRule | 擴充或 Jupiter 原生固定裝置 |
@Test(expected = E.class) | assertThrows(E.class, executable) |
@RunWith 到 @ExtendWith 是概念對應,而不是機械式重新命名,因為 JUnit 4 類別可以有一個執行器,而 Jupiter 會組合多個擴充。
JUnit 4 將選用的判斷訊息放在最前面:
assertEquals("unexpected greeting", expected, actual);Jupiter 將訊息放在最後:
assertEquals(expected, actual, "unexpected greeting");現有的 org.junit.Assert 呼叫可以保留,同時將註解與生命週期程式碼移至 Jupiter。分開轉換判斷可以減少每次遷移變更的規模。
規則需要個別處理。junit-jupiter-migrationsupport 成品會調整 ExternalResource、Verifier 與 ExpectedException 規則系列;@EnableRuleMigrationSupport 會啟用這些配接器。@EnableJUnit4MigrationSupport 也會辨識 JUnit 4 @Ignore。其他自訂規則需要 Jupiter 擴充或直接重寫固定裝置。
因此,受控遷移遵循三個階段:
- 加入 Vintage,建立一個以 Platform 為基礎的測試命令。
- 將類別轉換至 Jupiter,根據執行器與規則的行為,而不是名稱,替換它們。
- 當建置不再包含 JUnit 3 或 JUnit 4 測試後,移除 Vintage、JUnit 4 與遷移支援。
評論
在 GitHub 上閱讀或評論(發表評論需要登入)