JUnit 4使用手冊
筆者此前使用過JUnit 3,工作關系很長時間沒再碰Java了。最近重新接觸了一下,發現JUnit 4和3有較大區別,特總結一下JUnit 4的基本用法,供自己查閱也供朋友們參考。
一、JUnit簡介
JUnit由Kent Beck和ErichGamma開發,幾乎毫無疑問是迄今所開發的最重要的第三方Java庫,它也成為了Java語言事實上的標準單元測試庫。正如Martin Fowler所說,“在軟件開發領域,從來就沒有如此少的代碼起到了如此重要的作用”。JUnit引導并促進了測試先行的編程和測試驅動的開發。
JUnit 4是該庫三年以來最具里程碑意義的一次發布。它的新特性主要是通過采用Java 5中的標記annotation)而不是利用子類、反射或命名機制來識別測試,從而簡化測試代碼。用Kent的話來說,“JUnit 4 的主題是通過進一步簡化 JUnit,鼓勵更多的開發人員編寫更多的測試。”
二、JUnit4實踐
選擇一款IDE(Eclipse, NetBean, Idea等),基本上它們都全面支持JUnit了。創建工程后,為了避免代碼混亂,建議為單元測試代碼與被測試代碼分別創建單獨的目錄。首先,寫一個很簡單的方法,判斷輸入的郵箱地址是否符合規范:
1 |
public static boolean checkEmail(String email) { |
2 |
if (!email.matches( "[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+" )) { |
OK,一切準備就緒,我們開始編寫這個方法的單元測試用例。
1、測試由@Test注釋開始
2 |
public void checkEmail(){ |
3 |
assertEquals( true , RegexUtil.checkEmail( "add.dd@sina.com" )); |
以前版本的JUnit通過命名約定和反射來定位測試用例,要求測試方法以”test”開頭+方法名,并且測試類需要繼承TestCase。JUnit 4中簡化了這個操作,只需要在測試類中引入org.junit.Test,在測試方法前使用注解@Test,JUnit就可以偵測該測試方法了,保持了代碼的簡潔。
注:在JUnit4中仍然可以以原來的方式進行測試(繼承TestCase并在方法前加test)。但是如果這樣就最好不要使用注解。因為一旦繼承了TestCase,注解會失效,如果沒有test前綴,會報:AssertionFailedError: No tests found…異常。
2、Fixture
它是指在執行一個或者多個測試方法時需要的一系列公共資源或者數據,例如測試環境,測試數據等。JUnit專門提供了設置公共Fixture的方法,同一測試類中的所有測試方法都可以共用它來初始化Fixture和注銷Fixture。在以前的版本,JUnit使用SetUp和TearDown方法。在JUnit4中,使用注解org,junit.Before和org.junit.After。例如:
2 |
public void initialize (){……} |
4 |
public void dispose (){……} |
這樣,在每一個測試方法執行之前,JUnit會保證注解了Before的方法提前執行。當測試方法執行完畢后,JUnit調用注解了After的方法注銷測試環境。
注:@Before和@After修飾的是方法級別的。它們都可以修飾多個方法,但是方法執行的順序不能保證。
在JUnit 4中還引入了類級別的Fixture設置方法,即使用注解 BeforeClass和AfterClass。這兩種方法都使用 public static void 修飾,且不能帶有任何參數。類級別的Fixture僅會在測試類中所有測試方法執行之前和之后執行。
3、異常和測試時間
JUnit 4中引入了異常的測試,以前版本中異常測試是在拋出異常的代碼中放入try塊,然后在try塊的末尾加入fail語句。在JUnit 4中,可以使用@Test中的expected參數,它表示測試方法期望拋出的異常,如果運行測試并沒有拋出這個異常,則JUnit會認為這個測試沒有通過。例如:
1 |
@Test (expected= IndexOutOfBoundsException. class ) |
3 |
new ArrayList<Object>().get( 1 ); |
@Test的另一個參數timeout,用來指定被測試方法被允許運行的最長時間。如果測試方法運行時間超過了指定的毫秒數,則JUnit認為測試失敗。例如:
2 |
public void checkEmail(){ |
3 |
assertEquals( true , RegexUtil.checkEmail( "add.dd@sina.com" )); |
4、忽略測試方法
JUnit 4提供注解org.junit.Ignore用于暫時忽略某個測試方法。例如:
2 |
@Test (expected=UnsupportedDBVersionException. class ) |
3 |
public void unsupportedDBCheck(){ …… } |
5、測試用例的執行
JUnit中所有的測試用例都是由測試運行器執行的。JUnit提供了默認的測試運行器,但并沒有限制我們必須使用默認的運行器(所有的運行器都繼承自Runner)。相反,我們不僅可以定制自己的運行器,而且還可以為每個測試類指定使用某個運行器(使用@RunWith)。例如JUnit的自測代碼:
01 |
public class RunWithTest { |
02 |
private static String log; |
03 |
public static class ExampleRunner extends Runner { |
04 |
public ExampleRunner(Class<?> klass) { |
08 |
public void run(RunNotifier notifier) { |
12 |
public Description getDescription() { |
14 |
return Description.createSuiteDescription( "example" ); |
17 |
@RunWith (ExampleRunner. class ) |
18 |
public static class ExampleTest { |
20 |
@Test public void run() { |
22 |
JUnitCore.runClasses(ExampleTest. class ); |
23 |
assertTrue(log.contains( "plan" )); |
24 |
assertTrue(log.contains( "initialize" )); |
25 |
assertTrue(log.contains( "run" )); |
顯而易見,如果測試類沒有顯式的聲明使用哪一個測試運行器,JUnit 會啟動默認的測試運行器執行測試類。一般情況下,默認測試運行器可以應對絕大多數的測試要求;當使用 JUnit 提供的一些高級特性或者針對特殊需求定制 JUnit 測試方式時,顯式的聲明測試運行器就必不可少了。
6、測試套件Suite
正如JUnit以前版本中提供的Suite一樣,JUnit4提供了一種批量運行測試類的方法,以方便我們在每次進行系統測試時,只需執行若干測試套件而不是執行無數測試用例。我們只需創建一個空類,并填寫兩個注解。例如:
2 |
@Suite .SuiteClasses({TestCheckEmail. class , TestTimeUtil. class }) |
3 |
public class CustomizeRunner{ |
測試套件中不僅可以包含基本的測試類,而且可以包含其它的測試套件。但是,一定要保證測試套件之間沒有循環包含關系,否則將出現死循環。
7、參數化測試
當我們編寫了大量的單元測試方法后,我們發現這些方法其實大同小異,只是參數不同(測試邊界值或者測試異常值)。在以前的 JUnit版本上,并沒有好的解決方法,而現在我們可以使用JUnit提供的參數化測試方式解決這個問題。
首先在測試類中指定參數運行期@RunWith(Parameterized.class)。例如:
01 |
@RunWith (Parameterized. class ) |
02 |
public class TestWithParam { |
03 |
@Parameterized .Parameters |
04 |
public static List<Object[]> data() { |
05 |
return Arrays.asList( new Object[][]{ |
06 |
{ 0 , 0 }, { 1 , 1 }, { 2 , 1 }, { 3 , 2 }, { 4 , 3 }, { 5 , 5 }, { 6 , 8 },{ 10 , 55 } |
10 |
private int fExpected; |
11 |
public testWithParam( int input, int expected) { |
17 |
assertEquals(fExpected, Fibonacci.compute(fInput)); |
19 |
private static class Fibonacci{ |
20 |
public static int compute( int input){ |
27 |
else return compute(input- 1 )+compute(input- 2 ); |
在靜態方法data中,我們使用二維數組來構建測試所需要的參數列表,其中每個數組中的元素的放置順序只要和構造函數中的順序保持一致就可以了。
參考文獻:
1.單元測試利器 JUnit 4:http://www.ibm.com/developerworks/cn/java/j-lo-junit4/
2.JUnit 4 JavaDoc