JUnit 4是JUnit框架有史以來(lái)的最大改進(jìn),其主要目標(biāo)便是利用Java 5的Annotation特性簡(jiǎn)化測(cè)試用例的編寫。讓我們看看如何使用JUnit 4來(lái)進(jìn)行Unit測(cè)試。
我們使用的開(kāi)發(fā)環(huán)境是Eclipse 3.2,它已經(jīng)自帶了JUnit 4.1,你需要將JUnit 4 Library添加到項(xiàng)目用到的Library中。另外,必須使用JDK 5.0或更高版本。
要在Eclipse環(huán)境之外運(yùn)行JUnit,需要下載JUnit 4.1。
我們先看一個(gè)簡(jiǎn)單的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的,如果傳入?yún)?shù)2, -1,則期待的返回值應(yīng)為0.5F,但實(shí)際返回值為1.0F。
下面我們看看傳統(tǒng)的JUnit的TestCase:
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依賴反射來(lái)執(zhí)行每個(gè)以test開(kāi)頭的方法。然而,在最新的JUnit 4中,由于有了Annotation的支持,我們的測(cè)試方法不需要再以testXxx標(biāo)識(shí)了,而是寫上一個(gè)@Test標(biāo)注即可。例如:
@Test public void doAbs() {...}
甚至MathTest類也不必繼承自TestCase。你也許會(huì)想到,不繼承自TestCase就無(wú)法調(diào)用assertXxx方法了,正因?yàn)槿绱耍械腶ssertXxx方法全部以靜態(tài)方法被放入了Assert類,使用Assert.assertXxx()調(diào)用。如果使用
import static org.junit.Assert.*;
則原有的代碼不必改動(dòng)。
setUp()和tearDown()方法也依賴@Before和@After標(biāo)記,這樣做的最大的好處是在繼承體系內(nèi)不必?fù)?dān)心忘記了在setUp()方法中調(diào)用父類的super.setUp()方法,JUnit框架會(huì)自動(dòng)處理父類的@Before和@After標(biāo)記的方法。
并且,JUnit框架對(duì)@Before和@After的調(diào)用順序類似于類的構(gòu)造方法和析構(gòu)方法,即@Before按照父類到子類的順序調(diào)用,@After則相反,這樣保證了資源的正確獲取和釋放。
當(dāng)然,不再?gòu)?qiáng)迫必須使用setUp和tearDown作為方法名,可以使用更有意義的方法名,例如:initDatabase()和closeDatabase(),只要它們被標(biāo)注了@Before和@After即可。
來(lái)看看使用Annotation的MathTest:
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);
}
}
對(duì)測(cè)試異常,JUnit 4可以用expected=Exception.class來(lái)期待一個(gè)預(yù)期的異常,而不必編寫
try {
...
fail("No exception");
}
catch(Exception e) {
// OK!
}
來(lái)看看doDiv0測(cè)試,我們期待一個(gè)除數(shù)為0的ArithmeticException,因此編寫如下測(cè)試方法:
@Test(expected=ArithmeticException.class)
public void doDiv0() {
new Math().div(127, 0);
}
對(duì)于非常耗時(shí)的測(cè)試,@Test還有一個(gè)timeout來(lái)標(biāo)識(shí)該方法最長(zhǎng)執(zhí)行時(shí)間,超過(guò)此時(shí)間即表示該測(cè)試方法失敗:
@Test(timeout=1)
public void doLongTimeTask() {
double d = 0;
for(int i=1; i<10000000; i++)
d+=i;
}
以上方法若執(zhí)行時(shí)間超過(guò)1ms則測(cè)試失敗,由于依賴CPU的執(zhí)行速度,在不同的機(jī)器上測(cè)試結(jié)果也不同。
JUnit 4另一個(gè)較大的變化是引入了@BeforeClass和@AfterClass,它們?cè)谝粋€(gè)Test類的所有測(cè)試方法執(zhí)行前后各執(zhí)行一次。這是為了能在@BeforeClass中初始化一些昂貴的資源,例如數(shù)據(jù)庫(kù)連接,然后執(zhí)行所有的測(cè)試方法,最后在@AfterClass中釋放資源。
正如你能想到的,由于@BeforeClass和@AfterClass僅執(zhí)行一次,因此它們只能標(biāo)記靜態(tài)方法,在所有測(cè)試方法中共享的資源也必須是靜態(tài)引用:
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;
}
最后執(zhí)行測(cè)試用例,可以看到結(jié)果:

各個(gè)方法執(zhí)行順序如下:
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是在實(shí)例化MathTest之前調(diào)用的,因此不能在構(gòu)造方法中初始化共享資源。
最后需要注意的是由于Java 5的自動(dòng)Box/Unbox特性,在調(diào)用assertEquals()時(shí)要特別注意,如果你傳入:
assertEquals(100F, 100);
則按照自動(dòng)Box變?yōu)椋?/p>
assertEquals(new Float(100F), new Integer(100));
測(cè)試失敗,因?yàn)镕loat類和Integer類不是同一類型。
因此要特別注意float和double的測(cè)試。事實(shí)上對(duì)float和double應(yīng)使用
assertEquals(float, float, float delta);
assertEquals(double, double, double delta);
delta指定了兩個(gè)作比較的浮點(diǎn)數(shù)的相差范圍,在此范圍內(nèi)的兩個(gè)浮點(diǎn)數(shù)將認(rèn)為相等。可以傳入一個(gè)很小的數(shù)例如0.0001F。
JUnit 4非常適合使用Java 5的開(kāi)發(fā)人員,但是無(wú)法在Java 1.4中獲得這些好處,并且,也不與以前的版本兼容。因此,如果你正在使用Java 5,就可以考慮使用JUnit 4來(lái)編寫測(cè)試。
此文章來(lái)自:http://www.javaeedev.com/blog/article.jspx?articleId=ff80808112e766ee011312f144520061