简而言之:JRunner
關于JUnit測試要點的多篇教程的第四章介紹了該工具可交換測試運行器體系結構的目的,并介紹了一些可用的實現。 正在進行的示例通過編寫參數化測試的不同可能性擴大了主題。
由于我已經發布了JUnit Rules的介紹,因此我決定跳過關于該主題的已宣布部分。 相反,我對后者進行了較小的更新。
測試跑步者架構
不要害怕為了偉大而放棄美好。
約翰·洛克菲勒
在先前的文章中,我們學習了將某些xUnit測試模式[MES]與JUnit一起使用。 工具運行時的默認行為很好地支持了這些概念。 但是有時需要針對特定??的測試類型或目標更改或補充后者。
考慮例如集成測試 ,該測試通常需要在特定環境中運行。 或想象一組包含子系統規范的測試用例,應該為常見的測試執行而組成。
為此,JUnit支持使用各種類型的測試處理器。 因此,它在運行時將測試類實例化,測試執行和結果報告委托給此類處理器,這些處理器必須是org.junit.Runner子類型。
測試用例可以使用@RunWith注釋指定其預期的運行器類型。 如果未指定任何類型,那么運行時將選擇BlockJUnit4ClassRunner作為默認值。 負責每個測試運行一個新的測試實例,并調用諸如隱式設置或拆卸處理程序之類的生命周期方法(另請參見有關測試結構的章節)。
@RunWith( FooRunner.class ) public class BarTest {該代碼段顯示了如何將虛構的FooRunner指定為也是虛構的BarTest測試處理器。
通常,無需編寫自定義測試運行程序。 但是,如果需要的話, Michael Scharhag最近就對JUnit的運行器體系結構進行了很好的解釋。
看起來特殊測試運行程序的用法很簡單,所以讓我們看一下其中的幾個:
套房和類別
Suite可能是最著名的處理器之一。 它允許以分層或主題結構的方式運行測試和/或其他套件的集合。 注意,指定類本身通常沒有主體實現。 它帶有一系列測試類的注釋,這些類通過運行套件來執行:
@RunWith(Suite.class) @SuiteClasses( { NumberRangeCounterTest.class,// list of test cases and other suites } ) public class AllUnitTests {}但是,套件的結構功能受到一定限制。 因此,JUnit 4.8引入了鮮為人知的Categories概念。 這樣就可以定義自定義類別類型,例如單元測試,集成測試和驗收測試。 要將測試用例或方法分配給這些類別之一,必須提供Category注釋:
// definition of the available categories public interface Unit {} public interface Integration {} public interface Acceptance {}// category assignment of a test case @Category(Unit.class) public class NumberRangeCounterTest {[...] }// suite definition that runs tests // of the category 'Unit' only @RunWith(Categories.class) @IncludeCategory(Unit.class) @SuiteClasses( { NumberRangeCounterTest.class,// list of test cases and other suites } ) public class AllUnitTests {}帶Categories注釋類定義了只運行與指定類別匹配的類列表測試的套件。 通過包含和/或排除注釋來完成規范。 請注意,類別可以在Maven或Gradle構建中使用,而無需定義特定的套件類(請參見JUnit文檔的類別部分)。
有關類別的更多信息: John Ferguson Smart撰寫了有關使用JUnit類別對測試進行分組的詳細說明。
由于通常認為套件類列表和類別注釋的維護有些繁瑣,因此您可能更喜歡通過測試后綴名稱àFooUnitTest而不是FooTest進行分類。 這允許在運行時在類型范圍上過濾類別。
但是JUnit本身不支持此過濾,因此您可能需要一個特殊的運行器來動態收集可用的匹配測試。 Johannes Link的ClasspathSuite是提供適當實現的庫。 如果您碰巧在OSGi環境中進行集成測試,則Rüdiger的BundleTestSuite會對捆綁BundleTestSuite執行類似的操作。
在對如何將測試運行程序用于測試捆綁的第一印象之后,讓我們繼續本教程的示例,并進行一些更令人興奮的事情。
參數化測試
本教程中使用的示例都是關于編寫一個簡單的數字范圍計數器,該計數器從給定值開始傳遞一定數量的連續整數。 另外,計數器取決于存儲類型以保留其當前狀態。 有關更多信息,請參閱前面的章節。
現在,假定應將由構造函數參數初始化的NumberRangeCounter作為API提供。 因此,我們可以認為實例創建檢查給定參數的有效性是合理的。
我們可以指定適當的極端情況,并通過每個測試通過IllegalArgumentException予以確認。 使用帶有Java 8 Lambdas方法的Clean JUnit Throwable-Tests方法,這種驗證storage參數一定不能為null的測試可能看起來像這樣:
@Testpublic void testConstructorWithNullAsStorage() {Throwable actual = thrown( () -> new NumberRangeCounter( null, 0, 0 ) );assertTrue( actual instanceof IllegalArgumentException );assertEquals( NumberRangeCounter.ERR_PARAM_STORAGE_MISSING,actual.getMessage() );}請注意,我堅持使用JUnit內置功能進行驗證。 我將在另一篇文章中介紹特定匹配器庫( Hamcrest , AssertJ )的優缺點。
為了使該職位保持范圍,我還跳過了有關NPE是否比IAE更好的討論。
如果我們必須涵蓋很多這種極端情況,則上述方法可能會導致很多非常相似的測試。 JUnit提供了Parameterized實現,以減少這種冗余。 這個想法是為通用測試結構提供各種數據記錄。
為此,使用帶有@Parameters注釋的公共靜態方法來創建數據記錄作為對象數組的集合。 此外,測試用例需要一個帶有參數的公共構造函數,該參數與記錄提供的數據類型匹配。
參數化處理器針對由參數方法提供的每個記錄運行給定測試。 這意味著對于每種測試和記錄組合,都會創建一個新的測試類實例。 構造函數參數將存儲為字段,并且可以通過測試進行訪問以進行設置,練習和驗證:
@RunWith( Parameterized.class ) public class NumberRangeCounterTest {private final String message;private final CounterStorage storage;private final int lowerBound;private final int range;@Parameterspublic static Collection<Object[]> data() {CounterStorage dummy = mock( CounterStorage.class );return Arrays.asList( new Object[][] { { NumberRangeCounter.ERR_PARAM_STORAGE_MISSING, null, 0, 0 }, { NumberRangeCounter.ERR_LOWER_BOUND_NEGATIVE, dummy, -1, 0 },[...] // further data goes here... } );}public NumberRangeCounterTest(String message, CounterStorage storage, int lowerBound, int range ){this.message = message;this.storage = storage;this.lowerBound = lowerBound;this.range = range;}@Testpublic void testConstructorParamValidation() {Throwable actual = thrown( () -> new NumberRangeCounter( storage, lowerBound, range ) );assertTrue( actual instanceof IllegalArgumentException );assertEquals( message, actual.getMessage() );}[...] }盡管該示例確實減少了測試冗余,但至少在可讀性方面值得商bat。 最后,這通常取決于測試的數量和特定測試數據的結構。 但絕對不幸的是, 不使用任何記錄值的測試也將執行多次。
因此,參數化測試通常保存在單獨的測試用例中,通常感覺更像是一種解決方法,而不是適當的解決方案。 因此,一個聰明的人想到了提供一種可以避免上述問題的測試處理器的想法。
JUnitParams
JUnitParams庫提供JUnitParamsRunner和@Parameter類型。 param批注指定給定測試的數據記錄。 注意使用相同簡單名稱的JUnit批注的區別。 后者標記了提供數據記錄的方法!
可以使用JUnitParams重寫上面的測試方案,如以下代碼片段所示:
@RunWith( JUnitParamsRunner.class ) public class NumberRangeCounterTest {public static Object data() {CounterStorage dummy = mock( CounterStorage.class );return $( $( ERR_PARAM_STORAGE_MISSING, null, 0, 0 ),$( ERR_LOWER_BOUND_NEGATIVE, dummy, -1, 0 ) ); }@Test@Parameters( method = "data" )public void testConstructorParamValidation(String message, CounterStorage storage, int lowerBound, int range ) {Throwable actual = thrown( () -> new NumberRangeCounter( storage, lowerBound, range ) );assertTrue( actual instanceof IllegalArgumentException );assertEquals( message, actual.getMessage() );}[...] }雖然這當然更緊湊并且乍一看看上去更干凈,但仍有一些構造需要進一步說明。 $(...)方法是在JUnitParamsRunner (靜態導入)中定義的,是創建對象數組的快捷方式。 一旦習慣了,數據定義就會變得更具可讀性。
$快捷方式用于方法data以創建嵌套的對象數組作為返回值。 盡管運行程序期望在運行時嵌套數據數組,但它能夠將簡單的對象類型作為返回值來處理。
測試本身具有一個附加的@Parameters批注。 批注的方法聲明是指用于為測試提供聲明的參數的數據提供程序 。 方法名稱在運行時通過反射解析。 這是解決方案的缺點,因為它在編譯時不安全。
但是在其他用例場景中,您可以指定數據提供程序類或隱式值,因此不會受到這種折衷的影響。 有關更多信息,請參見例如該庫的快速入門指南 。
另一個巨大的優點是,現在只有那些測試針對使用@Parameters批注的數據記錄運行。 標準測試僅執行一次。 反過來,這意味著可以將參數化測試保留在設備的默認測試用例中。
包起來
以上各節概述了JUnit可交換測試運行器體系結構的意義和目的。 它介紹了套件和類別以顯示基本用法,并舉例說明了測試運行程序如何簡化編寫與數據記錄相關的測試的任務。
有關其他測試運行程序的列表,junit.org上的“ 測試運行程序”和“ 自定義運行程序 ”頁面可能是一個不錯的起點。 并且,如果您想知道標題圖片的Theories主題是什么,可以看看Florian Waibels在JUnit上 發表的文章– Practice和@Theory之間的區別 。
下次在Nutshell中使用JUnit時,我最后將介紹可用于驗證測試結果的各種斷言。
參考文獻
[MES] xUnit測試模式,Gerard Meszaros,2007年
翻譯自: https://www.javacodegeeks.com/2014/09/junit-in-a-nutshell-test-runners.html
總結
以上是生活随笔為你收集整理的简而言之:JRunner的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Apache Camel 2.14中的更
- 下一篇: 电脑怎么恢复出厂设置windows7台式