JUnit规则
第一次偶然發現JUnit @Rule批注時,我對此概念有些惱火。 在測試用例中擁有一個公共領域似乎有些奇怪,因此我不愿意定期使用它。 但是一段時間后,我習慣了這一點,事實證明,規則可以通過多種方式簡化編寫測試的過程。 這篇文章簡要介紹了該概念,并簡要列舉了一些規則的優點。
什么是JUnit規則?
讓我們從一個現成的JUnit規則開始。 TemporaryFolder是一個測試幫助程序,可用于為臨時內容1創建位于文件系統目錄下的文件和文件夾。 TemporaryFolder的有趣之處在于,它保證在測試方法完成時刪除其文件和文件夾2 。 為了按預期方式工作,必須將臨時文件夾實例分配給@Rule注釋字段,該字段必須是公共的(不是靜態的),并且是TestRule的子類型:
public class MyTest {@Rulepublic TemporaryFolder temporaryFolder = new TemporaryFolder();@Testpublic void testRun() throws IOException {assertTrue( temporaryFolder.newFolder().exists() );} }
它是如何工作的?
規則提供了一種攔截測試方法調用的可能性,就像AOP框架一樣。 與AspectJ中的周圍建議相比,您可以在實際測試執行之前和/或之后做一些有用的事情3 。 盡管這聽起來很復雜,但是卻很容易實現。
規則定義的API部分必須實現TestRule。 此接口稱為apply的唯一方法返回Statement 。 Statement s表示(簡單地說)在JUnit運行時中的測試,而Statement#evaluate()執行它們。 現在,基本思想是提供Statement包裝擴展,該包裝可以通過覆蓋Statement#evaluate()來進行實際貢獻:
public class MyRule implements TestRule {@Overridepublic Statement apply( Statement base, Description description ) {return new MyStatement( base );} }public class MyStatement extends Statement {private final Statement base;public MyStatement( Statement base ) {this.base = base;}@Overridepublic void evaluate() throws Throwable {System.out.println( 'before' );try {base.evaluate();} finally {System.out.println( 'after' );}} }MyStatement作為包裝器實現,在MyRule#apply(Statement,Destination)使用該包裝器包裝作為參數給出的原始語句。 很容易看出,包裝程序覆蓋了Statement#evaluate()在實際測試4之前和之后做一些事情。
下一個代碼片段顯示如何與上面的TemporaryFolder完全一樣地使用MyRule :
public class MyTest {@Rulepublic MyRule myRule = new MyRule();@Testpublic void testRun() {System.out.println( 'during' );} }啟動測試用例將導致以下控制臺輸出,這證明我們的示例規則可以按預期工作。 測試執行被我們的規則攔截和修改,以在測試的“期間”前后打印“之前”和“之后”:
before during after現在已經了解了基礎知識,下面讓我們看一下您可以使用規則執行的更有用的事情。
測試治具
從相應的維基百科部分引用的“測試裝置”是運行測試并期望獲得特定結果所必須具備的所有條件。 通常,通過處理單元測試框架的setUp()和tearDown()事件來創建固定裝置。
使用JUnit,這通常看起來像這樣:
public class MyTest {private MyFixture myFixture;@Testpublic void testRun1() {myFixture.configure1();// do some testing here}@Testpublic void testRun2() {myFixture.configure2();// do some testing here}@Beforepublic void setUp() {myFixture = new MyFixture();}@Afterpublic void tearDown() {myFixture.dispose();} }考慮您在許多測試中以上面顯示的方式使用特定的夾具。 在那種情況下,最好擺脫setUp()和tearDown()方法。 鑒于以上各節,我們現在知道可以通過更改MyFixture來實現TestRule來完成。 適當的Statement實現必須確保它調用MyFixture#dispose()并且看起來可能像這樣:
public class MyFixtureStatement extends Statement {private final Statement base;private final MyFixture fixture;public MyFixtureStatement( Statement base, MyFixture fixture ) {this.base = base;this.fixture = fixture;}@Overridepublic void evaluate() throws Throwable {try {base.evaluate();} finally {fixture.dispose();}} }有了這個,上面的測試可以重寫為:
public class MyTest {@Rulepublic MyFixture myFixture = new MyFixture();@Testpublic void testRun1() {myFixture.configure1();// do some testing here}@Testpublic void testRun2() {myFixture.configure2();// do some testing here} }在很多情況下,我開始欣賞使用規則編寫測試的更為緊湊的形式,但是可以肯定的是,這也是一個品味問題以及您認為更適合閱讀的內容5 。
帶有方法注釋的夾具配置
到目前為止,我已默默地忽略了TestRule#apply(Statement,Description)的Description參數。 通常, Description描述了將要運行或已經運行的測試。 但它也允許訪問有關底層java方法的一些反射信息。 除其他外,有可能讀取這種方法附帶的注釋。 這使我們能夠將規則與方法注釋結合起來,以方便配置TestRule 。
考慮以下注釋類型:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Configuration {String value(); }與MyFixture#apply(Statement,Destination)中的以下代碼段結合使用,該代碼讀取注釋為特定測試方法的配置值…
Configuration annotation= description.getAnnotation( Configuration.class ); String value = annotation.value(); // do something useful with value…上面演示MyFixture規則用法的MyFixture可以重寫為:
public class MyTest {@Rulepublic MyFixture myFixture = new MyFixture();@Test@Configuration( value = 'configuration1' )public void testRun1() {// do some testing here}@Test@Configuration( value = 'configuration2' )public void testRun2() {// do some testing here} }當然,由于注釋僅允許Enum , Class es或String文字作為參數,因此后一種方法存在局限性。 但是在某些用例中,這已經足夠了。 restfuse庫提供了一個很好的示例,該示例將規則與方法注釋結合使用。 如果您對現實世界的示例感興趣,則應查看Destination規則6的庫實現。
最后,剩下的唯一要說的是,我很想聽聽您關于可以用來簡化日常測試工作的JUnit規則的其他有用示例的信息:
參考:來自JCG合作伙伴 Frank Appel的JUnit規則 ,位于Code Affine博客上。
翻譯自: https://www.javacodegeeks.com/2012/11/junit-rules.html
總結
- 上一篇: 韩国现代手机官网(韩国现代科技中国有限公
- 下一篇: 高级ZK:异步UI更新和后台处理–第2部