JUnit 4使用手冊
筆者此前使用過JUnit 3,工作關(guān)系很長時間沒再碰Java了。最近重新接觸了一下,發(fā)現(xiàn)JUnit 4和3有較大區(qū)別,特總結(jié)一下JUnit 4的基本用法,供自己查閱也供朋友們參考。
一、JUnit簡介
JUnit由Kent Beck和ErichGamma開發(fā),幾乎毫無疑問是迄今所開發(fā)的最重要的第三方Java庫,它也成為了Java語言事實上的標準單元測試庫。正如Martin Fowler所說,“在軟件開發(fā)領(lǐng)域,從來就沒有如此少的代碼起到了如此重要的作用”。JUnit引導(dǎo)并促進了測試先行的編程和測試驅(qū)動的開發(fā)。
JUnit 4是該庫三年以來最具里程碑意義的一次發(fā)布。它的新特性主要是通過采用Java 5中的標記annotation)而不是利用子類、反射或命名機制來識別測試,從而簡化測試代碼。用Kent的話來說,“JUnit 4 的主題是通過進一步簡化 JUnit,鼓勵更多的開發(fā)人員編寫更多的測試。”
二、JUnit4實踐
選擇一款I(lǐng)DE(Eclipse, NetBean, Idea等),基本上它們都全面支持JUnit了。創(chuàng)建工程后,為了避免代碼混亂,建議為單元測試代碼與被測試代碼分別創(chuàng)建單獨的目錄。首先,寫一個很簡單的方法,判斷輸入的郵箱地址是否符合規(guī)范:
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
它是指在執(zhí)行一個或者多個測試方法時需要的一系列公共資源或者數(shù)據(jù),例如測試環(huán)境,測試數(shù)據(jù)等。JUnit專門提供了設(shè)置公共Fixture的方法,同一測試類中的所有測試方法都可以共用它來初始化Fixture和注銷Fixture。在以前的版本,JUnit使用SetUp和TearDown方法。在JUnit4中,使用注解org,junit.Before和org.junit.After。例如:
2 |
public void initialize (){……} |
4 |
public void dispose (){……} |
這樣,在每一個測試方法執(zhí)行之前,JUnit會保證注解了Before的方法提前執(zhí)行。當測試方法執(zhí)行完畢后,JUnit調(diào)用注解了After的方法注銷測試環(huán)境。
注:@Before和@After修飾的是方法級別的。它們都可以修飾多個方法,但是方法執(zhí)行的順序不能保證。
在JUnit 4中還引入了類級別的Fixture設(shè)置方法,即使用注解 BeforeClass和AfterClass。這兩種方法都使用 public static void 修飾,且不能帶有任何參數(shù)。類級別的Fixture僅會在測試類中所有測試方法執(zhí)行之前和之后執(zhí)行。
3、異常和測試時間
JUnit 4中引入了異常的測試,以前版本中異常測試是在拋出異常的代碼中放入try塊,然后在try塊的末尾加入fail語句。在JUnit 4中,可以使用@Test中的expected參數(shù),它表示測試方法期望拋出的異常,如果運行測試并沒有拋出這個異常,則JUnit會認為這個測試沒有通過。例如:
1 |
@Test (expected= IndexOutOfBoundsException. class ) |
3 |
new ArrayList<Object>().get( 1 ); |
@Test的另一個參數(shù)timeout,用來指定被測試方法被允許運行的最長時間。如果測試方法運行時間超過了指定的毫秒數(shù),則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、測試用例的執(zhí)行
JUnit中所有的測試用例都是由測試運行器執(zhí)行的。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 會啟動默認的測試運行器執(zhí)行測試類。一般情況下,默認測試運行器可以應(yīng)對絕大多數(shù)的測試要求;當使用 JUnit 提供的一些高級特性或者針對特殊需求定制 JUnit 測試方式時,顯式的聲明測試運行器就必不可少了。
6、測試套件Suite
正如JUnit以前版本中提供的Suite一樣,JUnit4提供了一種批量運行測試類的方法,以方便我們在每次進行系統(tǒng)測試時,只需執(zhí)行若干測試套件而不是執(zhí)行無數(shù)測試用例。我們只需創(chuàng)建一個空類,并填寫兩個注解。例如:
2 |
@Suite .SuiteClasses({TestCheckEmail. class , TestTimeUtil. class }) |
3 |
public class CustomizeRunner{ |
測試套件中不僅可以包含基本的測試類,而且可以包含其它的測試套件。但是,一定要保證測試套件之間沒有循環(huán)包含關(guān)系,否則將出現(xiàn)死循環(huán)。
7、參數(shù)化測試
當我們編寫了大量的單元測試方法后,我們發(fā)現(xiàn)這些方法其實大同小異,只是參數(shù)不同(測試邊界值或者測試異常值)。在以前的 JUnit版本上,并沒有好的解決方法,而現(xiàn)在我們可以使用JUnit提供的參數(shù)化測試方式解決這個問題。
首先在測試類中指定參數(shù)運行期@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 ); |
在靜態(tài)方法data中,我們使用二維數(shù)組來構(gòu)建測試所需要的參數(shù)列表,其中每個數(shù)組中的元素的放置順序只要和構(gòu)造函數(shù)中的順序保持一致就可以了。
參考文獻:
1.單元測試利器 JUnit 4:http://www.ibm.com/developerworks/cn/java/j-lo-junit4/
2.JUnit 4 JavaDoc