JUnit 實例講解

JUnit 4JUnit框架有史以來的最大改進,其主要目標便是利用Java 5Annotation特性簡化測試用例的編寫。讓我們看看如何使用JUnit 4來進行Unit測試。

請注意:本文主要介紹JUnit 4的最新特性和用法,並不會講解Unit測試的基礎。如果你對JUnit框架還不熟悉,請先參考Eclipse快速上手指南 (2) 使用JUnit測試一文,學習如何編寫JUnit測試。

我們使用的開發環境是Eclipse 3.2,它已經自帶了JUnit 4.1,你需要將JUnit 4 Library添加到項目用到的Library中。另外,必須使用JDK 5.0或更高版本。

要在Eclipse環境之外運行JUnit,需要下載JUnit 4.1,具體請訪問JUnit.org

 

我們先看一個簡單的Math類:

package com.javaeedev.junit4;
public class Math {

    public int abs(int value) {
        return value>=0 ? value : (-value);
    }

    public int div(int a, int b) {
        return a / b;
    }

    /**
     * BUG: if b less than 0!
     */
    public float exp(int a, int b) {
        float r = 1;
        for(int i=0; i<b; i++)
            r = r * a;
        return r;
    }
}

注意exp()方法是有Bug的,如果傳入參數2, -1,則期待的返回值應爲0.5F,但實際返回值爲1.0F

下面我們看看傳統的JUnitTestCase

public class MathTest extends TestCase {

    public void setUp() { super.setUp(); }
    public void tearDown() { super.tearDown(); }

    public void testAbs() { assertTrue(true); }
    public void testDiv() {...}
    public void testExp() {...}

}

JUnit依賴反射來執行每個以test開頭的方法。然而,在最新的JUnit 4中,由於有了Annotation的支持,我們的測試方法不需要再以testXxx標識了,而是寫上一個@Test標註即可。例如:

@Test public void doAbs() {...}

甚至MathTest類也不必繼承自TestCase。你也許會想到,不繼承自TestCase就無法調用assertXxx方法了,正因爲如此, 所有的assertXxx方法全部以靜態方法被放入了Assert類,使用Assert.assertXxx()調用。如果使用

import static org.junit.Assert.*;

則原有的代碼不必改動。

setUp()tearDown()方法也依賴@Before@After標記,這樣做的最大的好處是在繼承體系內不必擔心忘記了在 setUp()方法中調用父類的super.setUp()方法,JUnit框架會自動處理父類的@Before@After標記的方法。

並且,JUnit框架對@Before@After的調用順序類似於類的構造方法和析構方法,即@Before按照父類到子類的順序調用,@After則相反,這樣保證了資源的正確獲取和釋放。

當然,不再強迫必須使用setUptearDown作爲方法名,可以使用更有意義的方法名,例如:initDatabase()closeDatabase(),只要它們被標註了@Before@After即可。

來看看使用AnnotationMathTest

package com.javaeedev.junit4;

import static org.junit.Assert.*;

import org.junit.*;

public class MathTest {

    public MathTest() {
        System.out.println("new MathTest instance.");
    }

    @Before
    public void setUp() throws Exception {
        System.out.println("call @Before before a test method");
    }

    @After
    public void tearDown() throws Exception {
        System.out.println("call @After after a test method");
    }

    @Test
    public void doAbs() {
        Math math = new Math();
        assertEquals(200, math.abs(200));
        assertEquals(100, math.abs(-100));
        assertEquals(0, math.abs(0));
    }

    @Test
    public void doDiv() {
        Math math = new Math();
        assertEquals(5, math.div(100, 20));
        assertEquals(4, math.div(100, 21));
    }

    @Test(expected=ArithmeticException.class)
    public void doDiv0() {
        new Math().div(127, 0);
    }

    @Test(timeout=1)
    public void doLongTimeTask() {
        double d = 0;
        for(int i=1; i<10000000; i++)
            d+=i;
    }

    @Test
    public void testExp() {
        Math math = new Math();
        assertEquals(32f, math.exp(2, 5), 0.001f);
        assertEquals(1f, math.exp(2, 0), 0.001f);
        assertEquals(0.5f, math.exp(2, (-1)), 0.001f);
    }

}

對測試異常,JUnit 4可以用expected=Exception.class來期待一個預期的異常,而不必編寫

try {
  ...
  fail("No exception");
}
catch(Exception e) {
  // OK!
}

來看看doDiv0測試,我們期待一個除數爲0ArithmeticException,因此編寫如下測試方法:

@Test(expected=ArithmeticException.class)
public void doDiv0() {
    new Math().div(127, 0);
}

對於非常耗時的測試,@Test還有一個timeout來標識該方法最長執行時間,超過此時間即表示該測試方法失敗:

@Test(timeout=1)
public void doLongTimeTask() {
    double d = 0;
    for(int i=1; i<10000000; i++)
        d+=i;
}

以上方法若執行時間超過1ms則測試失敗,由於依賴CPU的執行速度,在不同的機器上測試結果也不同。

JUnit 4另一個較大的變化是引入了@BeforeClass@AfterClass,它們在一個Test類的所有測試方法執行前後各執行一次。這是爲了能在 @BeforeClass中初始化一些昂貴的資源,例如數據庫連接,然後執行所有的測試方法,最後在@AfterClass中釋放資源。

正如你能想到的,由於@BeforeClass@AfterClass僅執行一次,因此它們只能標記靜態方法,在所有測試方法中共享的資源也必須是靜態引用:

private static Object dbConnection;

@BeforeClass
public static void setUpBeforeClass() throws Exception {
    System.out.println("call @BeforeClass and init database connection");
    dbConnection = new Object();
}

@AfterClass
public static void tearDownAfterClass() throws Exception {
    System.out.println("call @AfterClass to release database connection");
    dbConnection = null;
}

各個方法執行順序如下:

call @BeforeClass and init database connection

new MathTest instance.
call @Before before a test method
call @After after a test method

new MathTest instance.
call @Before before a test method
call @After after a test method

...

call @AfterClass to release database connection

可以看到,@BeforeClass是在實例化MathTest之前調用的,因此不能在構造方法中初始化共享資源。

最後需要注意的是由於Java 5的自動Box/Unbox特性,在調用assertEquals()時要特別注意,如果你傳入:

assertEquals(100F, 100);

則按照自動Box變爲:

assertEquals(new Float(100F), new Integer(100));

測試失敗,因爲Float類和Integer類不是同一類型。

因此要特別注意floatdouble的測試。事實上對floatdouble應使用

assertEquals(float, float, float delta);
assertEquals(double, double, double delta);

delta指定了兩個作比較的浮點數的相差範圍,在此範圍內的兩個浮點數將認爲相等。可以傳入一個很小的數例如0.0001F 

JUnit 4非常適合使用Java 5的開發人員,但是無法在Java 1.4中獲得這些好處,並且,也不與以前的版本兼容。因此,如果你正在使用Java 5,就可以考慮使用JUnit 4來編寫測試。

ZIP包下載:javaeedev.googlecode.com/files/JUnit4Example.zip

參考

http://www.javaeedev.com/blog/article.jspx?articleId=ff80808112e766ee011312f144520061

 

 

2. @BeforeClass/@AfterClass 和 @Before/@After的區別

 

@BeforeClass and @AfterClass 

@Before and @After 

在一個類中只可以出現一次

在一個類中可以出現多次,即可以在多個方法的聲明前加上這兩個Annotaion標籤,執行順序不確定

方法名不做限制

方法名不做限制

在類中只運行一次

在每個測試方法之前或者之後都會運行一次

@BeforeClass父類中標識了該<span style="font-family: Times New Roma

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章