如何在JUnit 5中替换规则
最近發(fā)布的JUnit 5(又名JUnit Lambda) Alpha版本引起了我的興趣,在瀏覽文檔時,我注意到規(guī)則以及跑步者和階級規(guī)則都消失了。 根據(jù)文檔,這些部分競爭的概念已被單個一致的擴展模型取代。
多年來, Frank和我寫了一些規(guī)則來幫助執(zhí)行重復(fù)性任務(wù),例如測試SWT UI , 忽略某些環(huán)境中的測試 , 注冊(測試)OSGi服務(wù) , 在單獨的線程中運行測試等等。
因此,我對將現(xiàn)有規(guī)則轉(zhuǎn)換為新概念以使它們可以在JUnit 5上本地運行特別感興趣。為了探索擴展的功能,我選擇了兩個特性完全不同的規(guī)則,并嘗試將它們遷移到JUnit 5 。
這些實驗的重點是查看規(guī)則和擴展之間的概念已發(fā)生了變化。 因此,我選擇重寫JUnit 4意味著不考慮向后兼容性。
如果您有興趣從JUnit 4遷移到5或探索在JUnit 5中運行現(xiàn)有規(guī)則的可能性,則可能需要參加相應(yīng)的討論。
第一個候選對象是ConditionalIgnoreRule ,它與@ConditionalIgnore批注一起使用。 該規(guī)則評估需要用注釋指定的條件,并據(jù)此確定是否執(zhí)行測試。
另一個候選者是內(nèi)置的TemporaryFolder規(guī)則 。 顧名思義,它允許創(chuàng)建在測試完成時刪除的文件和文件夾。
因此,它在測試執(zhí)行之前和之后掛接,以創(chuàng)建一個根目錄以在其中存儲文件和文件夾并清理該目錄。 此外,它提供了實用程序方法來在根目錄中創(chuàng)建文件和文件夾。
擴展說明
在詳細(xì)介紹向擴展的遷移規(guī)則之前,讓我們簡要了解一下新概念。
測試執(zhí)行遵循一定的生命周期。 可以延長生命周期的每個階段都由一個接口表示。 擴展可以在某些階段表達興趣,因為它們實現(xiàn)了相應(yīng)的接口。
使用ExtendWith批注,測試方法或類可以表示它在運行時需要特定的擴展。 所有擴展都有一個公共的超級接口: ExtensionPoint 。 ExtensionPoint的類型層次結(jié)構(gòu)列出了擴展當(dāng)前可以掛接到的所有位置。
例如,下面的代碼應(yīng)用了一個虛構(gòu)的MockitoExtension來注入模擬對象:
@ExtendWith(MockitoExtension.class) class MockTest {@MockFoo fooMock; // initialized by extension with mock( Foo.class ) }MockitoExtension將提供一個默認(rèn)的構(gòu)造函數(shù),以便可以在運行時實例化它,并實現(xiàn)必要的擴展接口,以便能夠?qū)?#64;Mock注入到所有@Mock注釋的字段中。
條件忽略
規(guī)則的重復(fù)模式是提供帶有注釋的串聯(lián)服務(wù),該注釋用于標(biāo)記和/或配置希望使用該服務(wù)的測試方法。 在這里,ConditionalIgnoreRule檢查其運行的所有測試方法,并尋找ConditinalIgnore批注。 如果找到了這樣的注釋,則評估其條件,如果滿足,則忽略測試。
這是ConditionalIgnoreRule實際運行的樣子:
@Rule public ConditionalIgnoreRule rule = new ConditionalIgnoreRule();@Test @ConditionalIgnore( condition = IsWindowsPlatform.class ) public void testSomethingPlatformSpecific() {// ... }現(xiàn)在,讓我們看一下代碼在JUnit 5中的外觀:
@Test @DisabledWhen( IsWindowsPlatform.class ) void testSomethingPlatformSpecific() {// ... }首先,您會注意到注釋已更改其名稱。 為了匹配使用術(shù)語Disabled而不是被忽略的JUnit 5約定,該擴展還將其名稱更改為DisabledWhen 。
盡管DisabledWhen注釋是由DisabledWhenExtension驅(qū)動的,但是沒有任何東西表明需要擴展。 其原因被稱為元注釋,并且在查看DisabledWhen的聲明方式時可以最好地說明它們:
@Retention(RetentionPolicy.RUNTIME) @ExtendWith(DisabledWhenExtension.class) public @interface DisabledWhen {Class<? extends DisabledWhenCondition> value(); }注釋(元)帶有處理它的擴展名。 并且在運行時,JUnit 5測試執(zhí)行器負(fù)責(zé)其余的工作。 如果遇到帶注釋的測試方法,并且該注釋又由ExtendWith元注釋,則實例化各個擴展并將其包含在生命周期中。
真的很整潔嗎? 在不指定相應(yīng)規(guī)則的情況下注釋測試方法時,此技巧還可以避免疏忽。
在幕后, DisabledWhenExtension實現(xiàn)了TestExexutionCondition接口。 對于每個測試方法,都將調(diào)用其唯一的evaluate()方法,并且必須返回一個ConditionEvaluationResult ,該ConditionEvaluationResult確定是否應(yīng)該執(zhí)行測試。
其余代碼與以前基本相同。 查找并發(fā)現(xiàn)DisabledWhen批注時,將創(chuàng)建指定條件類的實例,并詢問是否應(yīng)執(zhí)行測試。 如果執(zhí)行被拒絕,則返回一個禁用的ConditionEvaluationResult ,并且框架將采取相應(yīng)措施。
臨時文件夾
在將TemporaryFolder規(guī)則變?yōu)楫惓V?#xff0c;讓我們看一下該規(guī)則的組成。 首先,該規(guī)則將在測試設(shè)置和拆卸期間設(shè)置并清理一個臨時文件夾。 但是,它還為測試提供了訪問在該根文件夾內(nèi)創(chuàng)建(臨時)文件和文件夾的方法的權(quán)限。
遷移到擴展后,不同的職責(zé)變得更加明顯。 以下示例顯示了如何使用它:
@ExtendWith(TemporaryFolderExtension.class) class InputOutputTestprivate TemporaryFolder tempFolder;@Testvoid testThatUsesTemporaryFolder() {F?ile f?ile = tempFolder.newF?ile();// ...} }TemporaryFolderExtension掛接到測試執(zhí)行生命周期中,以提供和清除臨時文件夾,并為所有TemporaryFolder字段提供此類實例。 而TemporaryFolder允許訪問在根文件夾中創(chuàng)建文件和文件夾的方法。
為了注入TemporaryFolder ,該擴展實現(xiàn)了InstancePostProcessor接口。 創(chuàng)建測試實例后立即調(diào)用其postProcessTestInstance方法。 在該方法中,它可以通過TestExtensionContext參數(shù)訪問測試實例,并且可以將TemporaryFolder注入所有匹配的字段中。
對于一個類聲明多個TemporaryFolder字段的極少數(shù)事件,每個字段都被分配一個新實例,并且每個實例都有其自己的根文件夾。
在此過程中創(chuàng)建的所有注入的TemporaryFolder實例都保存在一個集合中,以便稍后進行清理時可以對其進行訪問。
要在執(zhí)行測試后進行清理,需要實現(xiàn)另一個擴展接口: AfterEachExtensionPoint 。 每次測試完成后,將調(diào)用其唯一的afterEach方法。 并且此處的TemporaryFolderExtension實現(xiàn)清除所有已知的TemporaryFolder實例。
現(xiàn)在我們可以與TemporaryFolder規(guī)則的功能相提并論,現(xiàn)在還可以支持新功能:方法級依賴注入。
在JUnit 5中,現(xiàn)在允許方法具有參數(shù)。
這意味著我們的擴展程序不僅應(yīng)該能夠注入字段,而且還應(yīng)該能夠注入TemporaryFolder類型的方法參數(shù)。 希望創(chuàng)建臨時文件的測試可以請求注入TemporaryFolder如以下示例所示:
通過實現(xiàn)MethodParameterResolver接口,擴展可以參與解析方法參數(shù)。 對于測試方法的每個參數(shù),都會調(diào)用擴展的supports()方法來確定它是否可以為給定參數(shù)提供值。 對于TemporaryFolderExtension ,實現(xiàn)將檢查參數(shù)類型是否為TemporaryFolder并在這種情況下返回true 。 如果需要更廣泛的上下文,則當(dāng)前方法調(diào)用上下文和擴展上下文還提供了supports()方法。
現(xiàn)在,該擴展程序決定支持某個參數(shù),它的resolve()方法必須提供一個匹配的實例。 同樣,提供了周圍的環(huán)境。 TemporaryFolderExtension只是返回一個唯一的TemporaryFolder實例,該實例知道(臨時)根文件夾并提供在其中創(chuàng)建文件和子文件夾的方法。
但是請注意,聲明無法解析的參數(shù)被視為錯誤。 因此,如果遇到?jīng)]有匹配解析器的參數(shù),則會引發(fā)異常。
在擴展中存儲狀態(tài)
您可能已經(jīng)注意到, TemporaryFolderExtension保持其狀態(tài)(即,它已創(chuàng)建的臨時文件夾的列表),當(dāng)前是一個簡單字段。 盡管測試表明這確實可行,但是文檔中沒有地方指出在調(diào)用不同擴展名時都使用同一實例。 因此,如果JUnit 5此時更改其行為,則在這些調(diào)用期間狀態(tài)可能會丟失。
好消息是,JUnit 5提供了一種維護稱為Store的擴展?fàn)顟B(tài)的方法。 如文檔所述,它們?yōu)閿U展提供了保存和檢索數(shù)據(jù)的方法 。
該API與簡化Map相似,并且允許存儲鍵值對,獲取與給定鍵關(guān)聯(lián)的值以及刪除給定鍵。 鍵和值都可以是任意對象。 可以通過將TestExtensionContext作為參數(shù)傳遞給每個擴展方法(例如, beforeEach , afterEach )來到達存儲。每個TestExtensionContext實例都封裝了正在執(zhí)行當(dāng)前測試的上下文 。
例如,在beforeEach ,值將存儲在擴展上下文中,如下所示:
@Override public void beforeEach( TestExtensionContext context ) {context.getStore().put( KEY, ... ); }以后可以像這樣檢索:
@Override public void afterEach( TestExtensionContext context ) {Store store = context.getStore();Object value = store.get( KEY );// use value... }為了避免可能發(fā)生的名稱沖突,可以為某些命名空間創(chuàng)建存儲。 上面使用的context.getStore()方法獲取默認(rèn)名稱空間的存儲。 要獲取特定命名空間的存儲,請使用
context.getStore( Namespace.of( MY, NAME, SPACE );名稱空間是通過對象數(shù)組{ MY, NAME, SPACE }來定義的。
返還TemporaryFolderExtension以使用Store的練習(xí)留給讀者。
運行代碼
- 可以在以下GitHub存儲庫中找到此處討論的兩個擴展的尖峰實現(xiàn): https : //github.com/rherrmann/junit5-experiments
該項目設(shè)置為在安裝了Maven支持的Eclipse中使用。 但是在具有Maven支持的其他IDE中編譯和運行代碼并不難。
很自然,在這種早期狀態(tài)下,尚不支持直接在Eclipse中運行JUnit 5測試。 因此,要運行所有測試,可能需要使用“使用ConsoleRunner運行所有測試”啟動配置。 如果遇到麻煩,請參考我以前關(guān)于JUnit 5的文章中的“ 使用JUnit 5運行測試”部分,以獲得更多提示或發(fā)表評論。
總結(jié)如何在JUnit 5中替換規(guī)則
在這個小小的實驗過程中,我給人的印象是,擴展是JUnit 4中規(guī)則和朋友的完美替代品。最后,使用新方法很有趣,并且比現(xiàn)有功能更簡潔。
如果您發(fā)現(xiàn)用擴展尚無法完成的用例,我相信如果讓他們知道 JUnit 5團隊將不勝感激。
但請注意,在撰寫本文時,擴展程序正在進行中 。 該API被標(biāo)記為實驗性的,如有更改,恕不另行通知。 因此,現(xiàn)在實際遷移JUnit 4幫助程序可能還為時過早-除非您不介意將代碼調(diào)整為可能更改的API。
如果JUnit 5擴展引起了您的興趣,您可能還需要繼續(xù)閱讀文檔的相應(yīng)章節(jié) 。
翻譯自: https://www.javacodegeeks.com/2016/04/replace-rules-junit-5.html
總結(jié)
以上是生活随笔為你收集整理的如何在JUnit 5中替换规则的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 两全其美的
- 下一篇: 安装Maven和配置阿里云镜像