almost 2 years ago

Translated from "Part 2: Using JUnit 5", Mert Çalişkan, Java Magazine November/December 2016, page 20. Copyright Oracle Corporation.

Part 2: 使用 JUnit 5

整合建置工具與 IDE 執行第五版及先前版本的測試

在本文的第一部分,我提到 JUnit 5 將推出的新功能,接下來我提供更多關於框架的細節,以及與像 Maven 及 Gradle 等建置工具的整合。

本文所有的範例都基於 JUnit 5.0.0-M2,可以從專案首頁取得。

架構概要

我們從 Junit 的套件 (package) 結構開始,該結構在第一次 alpha 釋出後有做調整,JUnit 5 現在主要有 PlatformJupiterVintage 三個套件。目前的版本 M2,套件結構與模組如 Listing 1 所示 [譯註:翻譯時已經釋出 M3 了]。

Figure 1. The JUnit 5 architecture

JUnit Platform 套件是 Vintage 與 Jupiter 套件的基礎,它包含 junit-platform-engine 模組,該模組提供公開的 API 以整合第三方測試框架 (像是 Specsy,一個 JVM 語言用的 BDD 風格測試框架);junit-platform-launcher 模組提供啟動 API,讓建置工具與 IDE使用,在第五版之前,IDE 與測試程式碼都使用相同的 JUnit,新版以更加模組化的方式,提供良好的關注點分離,建置工具相關的程式與 API 分開於不同的模組。

junit-platform-runner 模組提供 API 讓 JUnit 5 的測試能在 JUnit 4 上執行;junit-platform-console 模組支援從終端機啟動 JUnit platform,能用命令列的方式執行 JUnit 4 及 JUnit 5 的測試,並傳回測試結果顯示於終端機中; junit-platform-surefire-provider 模組提供的 JUnitPlatformProvider 類別,與 Surefire (Surefire 是 Maven 測試週期中執行 JUnit 的 plug-in) 整合,可以透過 Maven 執行 JUnit 5 的測試;此外,junit-platform-gradle-plugin 模組提供與 Gradle 建置工具的整合,稍後會提到。

JUnit Vintage 套件提供能在 JUnit 5 上執行 JUnit 3 與 JUnit 4 測試的引擎,junit-vintage-engine 模組即是執行測試的引擎,JUnit 團隊支援先前的版本,鼓勵目前不管使用哪個版本的開發者升級到 JUnit 5,稍後我會描述如何執行 JUnit 4 的測試。

JUnit Jupiter 包裝新的 API 及擴充模型,還提供執行 JUnit 5 測試的引擎。junit-jupiter-apijunit-jupiter-engine 是此專案的兩個模組。如果您只宣告相依於 junitjupiter-engine 模組,就足以執行 JUnit 5 的測試,因為 junit-jupiter-apijunit-jupiter-engine 的上游模組 (可轉移相依,transitive dependency)。

配置工具以使用 JUnit 5

可以在 Maven 與 Gradle 中定義對 JUnit 5 的相依,此外,也可以直接透過終端機直接執行測試,有些 IDE 已經開始提供支援執行 JUnit 5 的測試,所以對於新框架的採用情況,看來是樂觀的。

與 Maven 的整合 在 Maven 中定義對 JUnit 5 的相依性就如 Listing 1 所示,如同先前提到的,不需要宣告相依於 junit-jupiter-api 模組,因為當我宣告相依 junit-jupiter-engine 時,它會被當成上游模組而自動抓取。

Listing 1.
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.0.0-M2</version>
    <scope>test</scope>
</dependency>

若您想停留在 JUnit 4.x,是可以加入 vintage 模式的相依性來使用 JUnit 5,如 Listing 2 所示。

Listing 2.
<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <version>4.12.0-M2</version>
    <scope>test</scope>
</dependency>

當宣告 vintage 模式時,JUnit 4.12 與 junit-platform-engine 會被當成上游模組自動抓取,方便起見,JUnit 團隊對齊 vintage 模組與目前 JUnit 最新的正式版本,寫本文時正是 4.12 版 [譯註:5.0.0 M3 仍然對齊 JUnit 4.12],在宣告完相依性後,該開始使用這些相依的模組來執行您的測試了,在 Maven 的建置週期設定中,如 Listing 3 所示,於 maven-surefire-plugin 的相依性裡加入 junit-platform-surefire-provider

Listing 3.
<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.19.1</version>
    <dependencies>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-surefire-provider</artifactId>
            <version>1.0.0-M2</version>
        </dependency>
    </dependencies>
</plugin>

JUnit 團隊開發 junit-platform-surefire-provider 協助透過 Surefire 的機制執行 JUnit Vintage 與 JUnit Jupiter 的測試,這目前還未支援 Surefire 進階的參數,像是 forkCountparallel,但我相信接下來的幾個改版,Surefire 會補足這落差,百分之百支援 JUnit 5。

與 Gradle 的整合 在 Gradle 中定義相依性和 Maven 類似,如 Listing 4 所示,在 Gradle 中加入 Jupiter 引擎與 API 的相依。

Listing 4.
dependencies {
    testCompile("org.junit.jupiter:+junit-jupiter-engine:5.0.0-M2")
}

若是要加入 Vintage 引擎,則如 Listing 5 所示。

Listing 5.
dependencies {
    testCompile("org.junit.vintage:+junit-vintage-engine:4.12.0-M2")
}

要讓 JUnit Gradle plug-in 加到建置中,如 Listing 6 所示,需宣告並套用到配置檔中。

Listing 6.
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.junit.platform:+junit-platform-gradle-plugin:1.0.0-M2'
    }
}

apply plugin: 'org.junit.platform.gradle.plugin'

Listing 7 所示,可以在 junitPlatform 指令中設定 JUnit Gradle plugin,像是我可以定義用來執行的引擎 (預設是全部開啟)、要納入或排除的標籤、設定名稱策略過濾要執行的測試類別、指定特定的報表輸出目錄,以及日誌管理員的設定。

Listing 7.
junitPlatform {
    engines {
        // include 'junit-jupiter', 'junit-vintage'

        // exclude 'custom-engine'

    }
    tags {
        // include 'fast'

        // exclude 'slow'

    }
    // includeClassNamePattern '.*Test'

    // enableStandardTestTask true

    // below is the default reports directory

    // "build/test-results/junit-platform"

    logManager 'org.apache.logging.log4j.jul.+LogManager'
}

與終端機的整合 命令列應用程式 ConsoleLauncher 讓您可以直接在終端機中執行 JUnit Platform,這程式可以像 Listing 8 般以指令啟動,建立所需 JAR 檔的類別路徑是必要的,所以確保您有正確版本的檔案。

Listing 8. [輸入 classpath 路徑需在同一行要]
java -cp
    /path/to/junit-platform-console-1.0.0-M2.jar:
    /path/to/jopt-simple-5.0.2.jar:
    /path/to/junit-platform-commons-1.0.0-M2.jar:
    /path/to/junit-platform-launcher-1.0.0-M2.jar:
    /path/to/junit-platform-engine-1.0.0-M2.jar:
    /path/to/junit-jupiter-engine-5.0.0-M2.jar:
    /path/to/junit-jupiter-api-5.0.0-M2.jar:
    /path/to/opentest4j-1.0.0-M1.jar:
    org.junit.platform.console.ConsoleLauncher -a

參數 -a 指執行所有測試,參數 -n 指定只執行類別全名 [譯註:包含 package 名稱] 滿足特定正規表示是的測試,雖然根據文件的說法,還可能變動,但仍有許多選項可以使用。

與 IDE 的整合 市面上的 Java IDE 快速地演進,為執行 JUnit 5 測試提供穩健的支援,在寫本文的時候,IntelliJ IDEA 目前的版本可以處理 JUnit 5 的測試,且以樹狀結構分別呈現 Jupiter 及 Vintage 的測試,Figure 2 顯示測試一連串 stack 操作的輸出例子,該測試類別中包含用新的 @Nested 加註的子測試類別,因此開啟建立巢狀的測試,並正確呈現在圖中。

Figure 2. Output from IntelliJ for nested tests

JUnitPlatform 的協助下,讓 JUnit 5 的測試能在 IDE 的 JUnit 4 平台上執行,Eclipse Neon 及 NetBeans 8.1 同樣支援執行 JUnit 5 的測試。

Vintage 模式提供向下相容

JUnitPlatform 類別是 JUnit 4 執行器的一個實作,有它的協助,JUnit Jupiter 的測試可以在 JUnit 4 上執行, JUnitPlatform 類別由 junitplatform-runner 模組提供,所以如 Listing 9 所示,要在 Maven 中加入對 Jupiter engine 的相依。

Listing 9.
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.0.0-M2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.platform</groupId>
    <artifactId>junit-platform-runner</artifactId>
    <version>1.0.0-M2</version>
    <scope>test</scope>
</dependency>

Listing 10 提供一個簡單的測試類別實作,可以看到 import 的宣告,測試類別是以 JUnit 5 實作,但宣告的執行器,讓它可以在 JUnit 4 的平台上執行,例如 Eclipse Neon。

Listing 10.
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

import static org.junit.jupiter.api.Assertions.assertTrue;

@RunWith(JUnitPlatform.class)
class SampleTest {

    @Test
    void sampleTest() {
        assertTrue(true);
    }
}

譯註:其實 Vintage 做蠻多事的,讓我在翻譯時,一直懷疑自己是不是搞錯意思了,但到官網查閱之後,確定沒有翻錯,它可以讓 JUnit 3 和 JUnit 4 的測試在不修改的情況下,能在 JUnit 5 (也就是 Jupiter) 上執行,同時,它也可以在 JUnit 4 上執行 JUnit 5 的測試 (須加上@RunWith(JUnitPlatform.class)),讓還未支援 JUnit 5 的 IDE 能以 JUnit 4 執行測試 (但不支援某些功能)。

結論

JUnit 團隊在 JUnit 最近的釋出有相當出色表現,新的套件結構可以看出整個框架被翻新,為接下來的釋出提供一個基礎,JUnit 5 幾乎解決了前個版本的所有限制,並提供建置工具、IDE 與第三方測試框架更好的整合支援,在善用 lambda 及新的擴充模型實作下,我相信 JUnit 仍會是最受歡迎的 Java 框架。

learn more
In-depth blog on the JUnit architecture
Using JUnit 5 in IntelliJ IDEA
JUnit team on Twitter

JUnit 5 特刊系列索引
Part 1: Unit 5 初探
“A Deep Dive into JUnit 5’s Extension Model,” page 25
“Interview with Kent Beck,” page 36
“ Mutation Testing: Automate the Search for Imperfect Tests,” page 43

譯者的告白
我還記得研究所 Pattern-oriented Software Design 的後半段,就是拿 JUnit 的架構與程式作為例子,學習 design pattern 如何應用在軟體設計中,當時還是 JUnit 3 的時代,沒想到再次看 JUnit 的架構介紹已經是 JUnit 5 了。話說,每次從 PDF 複製文字時,fi 的 f 就是會不見,所以 define 會變成 deine,Surefire 會變成 Sureire,若是要被翻譯的字也就算了,但像 Surefire 這種保留下來的名字,都還要檢查 f 是否不見了。

← Part 1: Unit 5 初探 本周雜記 (2016/12/4 ~ 2016/12/10) →