第一次偶然發現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規則的其他有用示例的信息:
- 通常由
System.getProperty( 'java.io.tmpdir' );
返回的目錄System.getProperty( 'java.io.tmpdir' );
? - 在查看
TemporaryFolder
的實現時,我必須注意,它不會檢查文件刪除是否成功。 這可能是打開的文件句柄的情況下,一個薄弱點? - 值得的是,您甚至可以用其他方法代替完整的測試方法?
- 包裝語句的委托放入
try...finally
塊中,以確保執行測試后的功能,即使測試失敗。 在這種情況下,一個AssertionError
會被拋出,并且不是在finally塊語句都將跳過? - 你可能注意到
TemporaryFolder
之初例子也不外乎夾具的使用情況? - 請注意,restfuse的
Destination
類實現了MethodRule
而不是TestRule
。 這篇文章基于最新的JUnit版本,其中MethodRule
被標記為@Deprecated
。TestRule
代替MethodRule
。 但是,鑒于此職位的知識,仍然應該很容易理解實現?
參考:來自JCG合作伙伴 Frank Appel的JUnit規則 ,位于Code Affine博客上。
翻譯自: https://www.javacodegeeks.com/2012/11/junit-rules.html