天天看點

JUnit 4使用手冊

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了。建立工程後,為了避免代碼混亂,建議為單元測試代碼與被測試代碼分别建立單獨的目錄。首先,寫一個很簡單的方法,判斷輸入的郵箱位址是否符合規範:

public static boolean checkEmail(String email) {
        if (!email.matches("[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+")) {
            return false;
        }
        return true;
    }
           

        OK,一切準備就緒,我們開始編寫這個方法的單元測試用例。

1、測試由@Test注釋開始

@Test 
    public void checkEmail(){
        assertEquals(true, RegexUtil.checkEmail("[email protected]"));
    }
           

        以前版本的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。例如:

@Before 
    public void initialize (){……}
    @After 
    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會認為這個測試沒有通過。例如:

@Test(expected= IndexOutOfBoundsException.class)
    public void empty() {
        new ArrayList<Object>().get(1);
    }
           

       @Test的另一個參數timeout,用來指定被測試方法被允許運作的最長時間。如果測試方法運作時間超過了指定的毫秒數,則JUnit認為測試失敗。例如:

@Test(timeout = 10)
    public void checkEmail(){
        assertEquals(true, RegexUtil.checkEmail("[email protected]"));
    }
           

4、忽略測試方法

       JUnit 4提供注解org.junit.Ignore用于暫時忽略某個測試方法。例如:

@Ignore(“db is down”)
    @Test(expected=UnsupportedDBVersionException.class)
    public void unsupportedDBCheck(){ ……  }
           

5、測試用例的執行

        JUnit中所有的測試用例都是由測試運作器執行的。JUnit提供了預設的測試運作器,但并沒有限制我們必須使用預設的運作器(所有的運作器都繼承自Runner)。相反,我們不僅可以定制自己的運作器,而且還可以為每個測試類指定使用某個運作器(使用@RunWith)。例如JUnit的自測代碼:

public class RunWithTest {
        private static String log;
        public static class ExampleRunner extends Runner {
            public ExampleRunner(Class<?> klass) {
                log+= "initialize";
            }
            @Override
            public void run(RunNotifier notifier) {
                log+= "run";
            }
            @Override
            public Description getDescription() {
                log+= "plan";
                return Description.createSuiteDescription("example");
            }
        }
        @RunWith(ExampleRunner.class)
        public static class ExampleTest {
        }
        @Test public void run() {
            log= "";
            JUnitCore.runClasses(ExampleTest.class);
            assertTrue(log.contains("plan"));
            assertTrue(log.contains("initialize"));
            assertTrue(log.contains("run"));
        }
    }
           

        顯而易見,如果測試類沒有顯式的聲明使用哪一個測試運作器,JUnit 會啟動預設的測試運作器執行測試類。一般情況下,預設測試運作器可以應對絕大多數的測試要求;當使用 JUnit 提供的一些進階特性或者針對特殊需求定制 JUnit 測試方式時,顯式的聲明測試運作器就必不可少了。

6、測試套件Suite

        正如JUnit以前版本中提供的Suite一樣,JUnit4提供了一種批量運作測試類的方法,以友善我們在每次進行系統測試時,隻需執行若幹測試套件而不是執行無數測試用例。我們隻需建立一個空類,并填寫兩個注解。例如:

@RunWith(Suite.class)
    @Suite.SuiteClasses({TestCheckEmail.class, TestTimeUtil.class})
    public class CustomizeRunner{
    }
           

       測試套件中不僅可以包含基本的測試類,而且可以包含其它的測試套件。但是,一定要保證測試套件之間沒有循環包含關系,否則将出現死循環。

7、參數化測試

        當我們編寫了大量的單元測試方法後,我們發現這些方法其實大同小異,隻是參數不同(測試邊界值或者測試異常值)。在以前的 JUnit版本上,并沒有好的解決方法,而現在我們可以使用JUnit提供的參數化測試方式解決這個問題。

        首先在測試類中指定參數運作期@RunWith(Parameterized.class)。例如:

@RunWith(Parameterized.class)
    public class TestWithParam {
        @Parameterized.Parameters
        public static List<Object[]> data() {
            return Arrays.asList(new Object[][]{
                {0, 0}, {1, 1}, {2, 1}, {3, 2}, {4, 3}, {5, 5}, {6, 8},{10,55}
            });
        }
        private int fInput;
        private int fExpected;
        public testWithParam(int input, int expected) {
            fInput = input;
            fExpected = expected;
        }
        @Test
        public void test() {
            assertEquals(fExpected, Fibonacci.compute(fInput));
        }
        private static class Fibonacci{
            public static int compute(int input){
                if(input ==0)
                    return 0;
                else if (input==1)
                    return 1;
                else if (input==2)
                    return 1;
                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