JUnit 5 架構、Maven 設定與 JUnit 4 遷移

最後更新: 2026-08-17 10:18:23 +0800 +0800
tags: Java | Junit | Maven | Testing
原文以英文撰寫。您正在閱讀AI翻譯版本。

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 專案 #

以下配置將正式類別與測試類別分開:

text
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、參數化測試支援與執行階段引擎。

xml
<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>
java
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;
    }
}
java
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 執行測試:

sh
mvn test

Jupiter 測試類別與方法可以使用套件私有可見性。測試方法不得是 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 版本保持相容:

xml
<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 套件,且可以共存於一個模組:

xml
<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 4Jupiter
@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 將選用的判斷訊息放在最前面:

java
assertEquals("unexpected greeting", expected, actual);

Jupiter 將訊息放在最後:

java
assertEquals(expected, actual, "unexpected greeting");

現有的 org.junit.Assert 呼叫可以保留,同時將註解與生命週期程式碼移至 Jupiter。分開轉換判斷可以減少每次遷移變更的規模。

規則需要個別處理。junit-jupiter-migrationsupport 成品會調整 ExternalResource、Verifier 與 ExpectedException 規則系列;@EnableRuleMigrationSupport 會啟用這些配接器。@EnableJUnit4MigrationSupport 也會辨識 JUnit 4 @Ignore。其他自訂規則需要 Jupiter 擴充或直接重寫固定裝置。

因此,受控遷移遵循三個階段:

  1. 加入 Vintage,建立一個以 Platform 為基礎的測試命令。
  2. 將類別轉換至 Jupiter,根據執行器與規則的行為,而不是名稱,替換它們。
  3. 當建置不再包含 JUnit 3 或 JUnit 4 測試後,移除 Vintage、JUnit 4 與遷移支援。

參考資料 #

評論

在 GitHub 上閱讀或評論(發表評論需要登入)