上周我們剛剛搭建好了 JUnit 5 的環(huán)境,現(xiàn)在我們可以寫測試了。這節(jié)讓我們來寫它幾個吧!
概述
本文章是這個 JUnit 5 系列的一部分:
· 環(huán)境搭建
· 基礎入門
· 架構體系
· 擴展模型(Extension Model)
· 條件斷言
· 注入
· 動態(tài)測試
...
(如果不喜歡看文章,你可以戳這里看我的演講,或者看一下近的 vJUG 講座,或者我在 DevoxxPL 上的 PPT。
本系列文章都基于 Junit 5發(fā)布的先行版 Milestone 2。它可能會有變化。如果有新的里程碑(milestone)版本發(fā)布,或者試用版正式發(fā)行時,我會再來更新這篇文章。
這里要介紹的多數(shù)知識你都可以在 JUnit 5 用戶指南 中找到(這個鏈接指向的是先行版 Milestone 2,想看的新版本文檔的話請戳這里),并且指南還有更多的內容等待你發(fā)掘。下面的所有代碼都可以在 我的 Github 上找到。
設計哲學
新的架構設計(這個我們日后聊),其關注點在高擴展性。如果后面出現(xiàn)了什么神之測試技術(至少對我們廣大 Java?來說很神的),它們也可能在 JUnit 5 的架構下被實現(xiàn)。
不過當前來說,涉及的基礎知識與 JUnit 4 是非常相似的。JUnit 5 的改動并不激進,相反它的優(yōu)化歷程是小心翼翼,小步迭代的。因此,開發(fā)者應該會對新的 API 感到非常熟悉。至少我是這樣的,我相信你也不會感覺陌生:
class Lifecycle {
@BeforeAll
static void initializeExternalResources() {
System.out.println("Initializing external resources...");
}
@BeforeEach
void initializeMockObjects() {
System.out.println("Initializing mock objects...");
}
@Test
void someTest() {
System.out.println("Running some test...");
assertTrue(true);
}
@Test
void otherTest() {
assumeTrue(true);
System.out.println("Running another test...");
assertNotEquals(1, 42, "Why wouldn't these be the same?");
}
@Test
@Disabled
void disabledTest() {
System.exit(1);
}
@AfterEach
void tearDown() {
System.out.println("Tearing down...");
}
@AfterAll
static void freeExternalResources() {
System.out.println("Freeing external resources...");
}
}
是吧?這里并沒有很大的改動。
JUnit 5 預備
包可見性
JUnit 5 明顯的變化應該是,不再需要手動將測試類與測試方法為 public 了。包可見的訪問級別足夠了。當然,私有(private)訪問還是不行的。我認為這個變化是合理的,也符合我們對可見性的一般直覺。
這很好!至少可以少打幾個字母了。不過,我相信你也不是每次都手打這幾個字母的,是吧?盡管如此還是很好,少一些關鍵字,你在看測試的時候也少些切換。
測試的生命周期
@Test
JUnit 中基本的注解非 @Test 莫屬了。它會標記方法為測試方法,以便構建工具和 IDE 能夠識別并執(zhí)行它們。
它的 API 和作用并沒有變化,不過它不再接受任何參數(shù)了。若要測試是否拋出異常,你可以通過新的斷言 API 來做到;不過我所知,目前還沒有超時選項timeout的替代品。
與 JUnit 4一樣,JUnit 5 會為每個測試方法創(chuàng)建一個新的實例。
Before 和 After
你可能需要執(zhí)行一些代碼來在測試執(zhí)行前后完成一些初始化或銷毀的操作。在 JUnit 5 中,有4個注解你可能會用于如此工作:
@BeforeAll
只執(zhí)行一次,執(zhí)行時機是在所有測試和 @BeforeEach 注解方法之前。
@BeforeEach
在每個測試執(zhí)行之前執(zhí)行。
@AfterEach
在每個測試執(zhí)行之后執(zhí)行。
@AfterAll
只執(zhí)行一次,執(zhí)行時機是在所有測試和 @AfterEach 注解方法之后。
因為框架會為每個測試創(chuàng)建一個單獨的實例,在 @BeforeAll/@AfterAll 方法執(zhí)行時尚無任何測試實例誕生。因此,這兩個方法必須定義為靜態(tài)方法。
注解了同樣一個注解的不同方法,其執(zhí)行次序是不可預知的,包括對繼承來的方法也適用。這是開發(fā)團隊經(jīng)過審慎思考后的決定,即把單元測試與集成測試的關注點分開。集成測試可能需要方法間更緊密的協(xié)作,但一個單元測試不應該對其他的單元測試有所依賴。而對于集成測試——也叫場景測試——的支持,也已在團隊的計劃中。
除了名字有所不同,這幾個注解與 JUnit 4 中的注解工作方式完全一樣。無獨有偶,跟主流意見一致,我也覺得這個新的命名不能說服我其必要性。這個 issue 下有更多的討論。
禁用測試
今兒星期五,抬頭一看已經(jīng)4點半,無心工作的你想回家了?完全理解,在測試上怒拍一個 @Disabled 注解即可。有良心的話寫個忽略測試的理由是極好的,不過也可以不帶此參數(shù)。
@Test
@Disabled("你丫是存心跑不過的是不?!")
void failingTest() {
assertTrue(false);
}
測試類的生命周期
JUnit 團隊發(fā)布的第一版原型中,包含了一個對 測試類的生命周期 的描述,有意思的是,這個特性在 alpha 版本的發(fā)布中未被加入。這個生命周期模型建議,在被測類的多個測試方法中使用一個同樣的實例,因為這樣我們可以通過改變對象的狀態(tài),進而實現(xiàn)在多個測試方法中的交互。(我也再說一遍,這更像是 場景測試 要管的事。)
正如我在第一版公測時所說,這樣的特性99%的場景下是有害的,只有另外1%的場合下才有真正的用處。我只能說,還好這個特性被摒棄了。想想你的單元測試,如果它們必須靠在方法間維護狀態(tài)來工作,這畫面簡直太美我不敢看?。
斷言
如果說 @Test、@Before...、@After... 等注解是一個測試套件的骨架,那么斷言是它的心臟。準備好測試實例、執(zhí)行了被測類的方法以后,斷言能確保你得到了想要的結果。否則,說明當前測試失敗了。
常規(guī)斷言
一般的斷言,無非是檢查一個實例的屬性(比如,判空與判非空等),或者對兩個實例進行比較(比如,檢查兩個實例對象是否相等)等。無論哪種檢查,斷言方法都可以接受一個字符串作為后一個可選參數(shù),它會在斷言失敗時提供必要的描述信息。如果提供出錯信息的過程比較復雜,它也可以被包裝在一個 lambda 表達式中,這樣,只有到真正失敗的時候,消息才會真正被構造出來。
@Test
void assertWithBoolean() {
assertTrue(true);
assertTrue(this::truism);
assertFalse(false, () -> "Really " + "expensive " + "message" + ".");
}
boolean truism() {
return true;
}
@Test
void assertWithComparison() {
List<String> expected = asList("element");
List<String> actual = new LinkedList<>(expected);
assertEquals(expected, actual);
assertEquals(expected, actual, "Should be equal.");
assertEquals(expected, actual, () -> "Should " + "be " + "equal.");
assertNotSame(expected, actual, "Obviously not the same instance.");
}
如你所見,JUnit 5 的 API 并無太多變化。斷言方法的命名是一樣的,方法同樣接受兩個參數(shù),分別是一個期望值與一個實際值。
期望值與實際值的傳入順序非常重要,無論是對于理解測試的內容,還是理解失敗時的錯誤信息,但有時還是很容易弄錯,這點很坑。不過仔細想想,也沒什么更好的辦法,除非你自己創(chuàng)建一個新的斷言框架。既然市面上已有對應的產(chǎn)品如 Hamcrest (ugh!) 和AssertJ (yeah!譯者表示:不太清楚這歡呼的梗在哪里)等,再浪費有限的時間去造輪子明顯不值得。畢竟重要的是保證你的斷言庫專注于一件事,借鑒已有實現(xiàn)可以節(jié)省成本。
哦對了,失敗信息現(xiàn)在是作為后傳入的參數(shù)了。我很喜歡這個細節(jié),因為,它讓你專注于真正重要之事——那兩個需被斷言的值。由于擁抱了 Java 8 的緣故,真值斷言方法現(xiàn)在也接受 supplier 參數(shù)了,又是一個暖心的小細節(jié)。
擴展斷言
除了那種一般的檢查特定實例或屬性的斷言外,還有一些其他類型的斷言。
這里要講的第一個甚至都不是個真正的斷言,它做的事是強行讓測試失敗,并提供一個失敗信息。
@Test
void failTheTest() {
fail("epicly");
}