提要 本文將向你介紹如何實(shí)現(xiàn)從JUnit 3.8向JUnit 4的遷移。同時(shí),還討論JUnit 4中的一些新特征,特別包括對注解的使用;最后,簡要介紹這個(gè)新版本的IDE集成現(xiàn)狀。
一、 引言
在本文開始,我將假定,你已經(jīng)了解由Kent Beck和Erich Gamma發(fā)明的這個(gè)Java單元測試框架并因此而略過必要的簡介。所以,我將集中分析從JUnit 3.8到最新版本-JUnit 4的遷移過程以及其在IDE和Ant中的集成。
JUnit 4是一種與其之前的版本完全不同的API,它根據(jù)Java 5.0中的新特征(注解,靜態(tài)導(dǎo)入等)構(gòu)建而成。如你所見,JUnit 4更簡單、更豐富和更易于使用,而且它引入了更為靈活的初始化和清理工作,還有限時(shí)的和參數(shù)化測試用例。
代碼實(shí)例最能說明問題。因此,在本文中,我將使用一個(gè)例子來展示不同的測試用例:一個(gè)計(jì)算器。該示例計(jì)算器很簡單,效率并不高,甚至還有一些錯(cuò)誤;它僅 僅操作整數(shù),并且把結(jié)果存儲(chǔ)在一個(gè)靜態(tài)變量中。Substract方法并不返回一個(gè)有效的結(jié)果,而且也沒有實(shí)現(xiàn)乘法運(yùn)算,而且看上去在 squareRoot方法中還存在一個(gè)錯(cuò)誤:無限循環(huán)。這些錯(cuò)誤將幫助說明使用JUnit 4進(jìn)行測試的有效性。你可以打開和關(guān)閉這個(gè)計(jì)算器,而且你可以清除這些結(jié)果。下面是其實(shí)現(xiàn)代碼:
二、 遷移一個(gè)測試類
現(xiàn)在,我將把一個(gè)已經(jīng)使用JUnit 3.8編寫成的簡單的測試類遷移到JUnit 4。這個(gè)類有一些缺陷:它沒有測試所有的業(yè)務(wù)方法,而且看上去在testDivide方法中還存在一個(gè)錯(cuò)誤(8/2不等于5)。因?yàn)檫€沒有實(shí)現(xiàn)乘法運(yùn)算功能,所以對其測試將被忽略。
下面,我們把兩個(gè)版本的框架之間的差別以粗體顯示出現(xiàn)于表格1中。
表格1.分別以JUnit 3.8和JUnit 4實(shí)現(xiàn)的CaculatorTest。
JUnit 3.8
JUnit 4
三、 包
首先,你可以看到,JUnit 4使用org.junit.*包而JUnit 3.8使用的是junit.framework.*。當(dāng)然,為了向后兼容性起見,JUnit 4jar文件發(fā)行中加入了這兩種包。
四、 繼承
在中,測試類不必再擴(kuò)展junit.framework.TestCase;事實(shí)上,它們不必須擴(kuò)展任何內(nèi)容。但是,JUnit 4中使用的是注解。為了以一個(gè)測試用例方式執(zhí)行,一個(gè)JUnit 4類中至少需要一個(gè)@Test注解。例如,如果你僅使用@Before和@After注解而沒有至少提供一個(gè)@Test方法來編寫一個(gè)類,那么,當(dāng)你試圖 執(zhí)行它時(shí)將得到一個(gè)錯(cuò)誤:
java.lang.Exception: No runnable methods.
五、 斷言(Assert)方法
因?yàn)樵贘Unit 4中一個(gè)測試類并不繼承自TestCase(在JUnit 3.8中,這個(gè)類中定義了assertEquals()方法),所以你必須使用前綴語法(舉例來說,Assert.assertEquals())或者 (由于JDK5.0)靜態(tài)地導(dǎo)入Assert類。這樣以來,你就可以完全象以前一樣使用assertEquals方法(舉例來說, assertEquals())。
另外,在JUnit 4中,還引入了兩個(gè)新的斷言方法,它們專門用于數(shù)組對象的比較。如果兩個(gè)數(shù)組包含的元素都相等,那么這兩個(gè)數(shù)組就是相等的。
由于JDK 5.0的自動(dòng)裝箱機(jī)制的出現(xiàn),原先的12個(gè)assertEquals方法全部去掉了。例如,原先JUnit 3.8中的assertEquals(long,long)方法在JUnit 4中要使用assertEquals(Object,Object)。對于assertEquals(byte,byte)、assertEquals (int,int)等也是這樣。這種改進(jìn)將有助于避免反模式。
在JUnit 4中,新集成了一個(gè)assert關(guān)鍵字(見我們的例子中的divide()方法)。你可以象使用assertEquals方法一樣來使用它,因?yàn)樗鼈兌紥?出相同的異常(java.lang.AssertionError)。JUnit 3.8的assertEquals將拋出一個(gè)junit.framework.AssertionFailedError。注意,當(dāng)使用assert時(shí), 你必須指定Java的"-ea"參數(shù);否則,斷言將被忽略。
六、 預(yù)設(shè)環(huán)境(Fixture)
Fixture是在測試期間初始化和釋放任何普通對象的方法。在JUnit 3.8中,你要使用setUp()來實(shí)現(xiàn)運(yùn)行每一個(gè)測試前的初始化工作,然后使用tearDown()來進(jìn)行每個(gè)測試后的清理。這兩個(gè)方法在 TestCase類中都得到重載,因此都被唯一定義。注意,我在這個(gè)Setup方法使用的是Java5.0內(nèi)置的@Override注解-這個(gè)注解指示該 方法聲明要重載在超類中的方法聲明。在JUnit 4中,則代之使用的是@Before和@After注解;而且,可以以任何命名(在我們的例子中是clearCalculator())來調(diào)用這些方法。 在本文后面,我將更多地解釋這些注解。
七、 測試
JUnit 3.8通過分析它的簽名來識(shí)別一個(gè)測試方法:方法名必須以"test"為前綴,它必須返回void,而且它必須沒有任何參數(shù)(舉例來說, publicvoidtestDivide())。一個(gè)不遵循這個(gè)命名約定的測試方法將被框架簡單地忽略,而且不拋出任何異常(指示發(fā)生了一個(gè)錯(cuò)誤)。
JUnit 4不使用與JUnit 3.8相同的約定。一個(gè)測試方法不必以'test'為前綴,但是要使用@Test注解。但是,正如在前一個(gè)框架中一樣,一個(gè)測試方法也必須返回void并 且是無參數(shù)的。在JUnit 4中,可以在運(yùn)行時(shí)刻控制這個(gè)要求,并且不符合要求的話會(huì)拋出一個(gè)異常:
java.lang.Exception: Method xxx should have no parameters
java.lang.Exception: Method xxx should be void
@Test注解支持可選參數(shù)。它聲明一個(gè)測試方法應(yīng)該拋出一個(gè)異常。如果它不拋出或者如果它拋出一個(gè)與事先聲明的不同的異常,那么該測試失敗。在我們的例子中,一個(gè)整數(shù)被零除應(yīng)該引發(fā)一個(gè)ArithmeticException異常。
八、 忽略一個(gè)測試
記住,不能執(zhí)行多個(gè)方法。然而,如果你不想讓測試失敗的話,你可以僅僅忽略它。那么,在JUnit 3.8中,我們是如何實(shí)現(xiàn)臨時(shí)禁止一個(gè)測試的呢?方法是:通過注釋掉它或者改變命名約定,這樣測試運(yùn)行機(jī)就無法找到它。在我的例子中,我使用了方法名 notReadyYetTestMultiply()。它沒有以"test"開頭,所以它不會(huì)被識(shí)別出來。現(xiàn)在的問題是,在成百上千的測試中間,你可能記 不住重命名這個(gè)方法。
在JUnit 4中,為了忽略一個(gè)測試,你可以注釋掉一個(gè)方法或者刪除@Test注解(你不能再改變命名約定,否則將拋出一個(gè)異常)。然而,該問題將保留:該運(yùn)行機(jī)將不 報(bào)告這樣一個(gè)測試。現(xiàn)在,你可以把@Ignore注解添加到@Test的前面或者后面。測試運(yùn)行機(jī)將報(bào)告被忽略的測試的個(gè)數(shù),以及運(yùn)行的測試的數(shù)目和運(yùn)行 失敗的測試數(shù)目。注意,@Ignore使用一個(gè)可選參數(shù)(一個(gè)String),如果你想記錄為什么一個(gè)測試被忽略的話。
九、 運(yùn)行測試
在JUnit 3.8中,你可以選擇使用若干運(yùn)行機(jī):文本型,AWT或者Swing。JUnit 4僅僅使用文本測試運(yùn)行機(jī)。注意,JUnit 4不會(huì)顯示任何綠色條來通知你測試成功了。如果你想看到任何類型的綠色的話,那么你可能需要使用JUnit擴(kuò)展或一種集成了JUnit的IDE(例如 IDEA或者Eclipse)。
首先,我想使用老式但好用的junit.textui.TestRunner來運(yùn)行該JUnit 3.8測試類(考慮到使用assert關(guān)鍵字,我使用了-ea參數(shù))。
java -ea junit.textui.TestRunner junit3.CalculatorTest
TestDivide產(chǎn)生一個(gè)錯(cuò)誤,因?yàn)閿嘌源_定了8/2不等于5。TestSubstract產(chǎn)生一個(gè)失敗,因?yàn)?0-2應(yīng)該等于8,但是在這個(gè)實(shí)現(xiàn)中存在一個(gè)錯(cuò)誤:它返回9。
現(xiàn)在,我使用新的org.junit.runner.JUnitCore運(yùn)行機(jī)來運(yùn)行這兩個(gè)類。注意,它能執(zhí)行JUnit 4和JUnit 3.8測試,甚至是這二者的結(jié)合。
第一個(gè)非常明顯的區(qū)別是,JUnit版本號(hào)被顯示于控制臺(tái)中(4.1)。第二個(gè)區(qū)別是,JUnit 3.8區(qū)分失敗和錯(cuò)誤;JUnit 4則僅使用失敗進(jìn)行簡化。一個(gè)新奇的地方是,字母"I",它顯示一個(gè)測試被忽略。
一、 引言
在本文開始,我將假定,你已經(jīng)了解由Kent Beck和Erich Gamma發(fā)明的這個(gè)Java單元測試框架并因此而略過必要的簡介。所以,我將集中分析從JUnit 3.8到最新版本-JUnit 4的遷移過程以及其在IDE和Ant中的集成。
JUnit 4是一種與其之前的版本完全不同的API,它根據(jù)Java 5.0中的新特征(注解,靜態(tài)導(dǎo)入等)構(gòu)建而成。如你所見,JUnit 4更簡單、更豐富和更易于使用,而且它引入了更為靈活的初始化和清理工作,還有限時(shí)的和參數(shù)化測試用例。
代碼實(shí)例最能說明問題。因此,在本文中,我將使用一個(gè)例子來展示不同的測試用例:一個(gè)計(jì)算器。該示例計(jì)算器很簡單,效率并不高,甚至還有一些錯(cuò)誤;它僅 僅操作整數(shù),并且把結(jié)果存儲(chǔ)在一個(gè)靜態(tài)變量中。Substract方法并不返回一個(gè)有效的結(jié)果,而且也沒有實(shí)現(xiàn)乘法運(yùn)算,而且看上去在 squareRoot方法中還存在一個(gè)錯(cuò)誤:無限循環(huán)。這些錯(cuò)誤將幫助說明使用JUnit 4進(jìn)行測試的有效性。你可以打開和關(guān)閉這個(gè)計(jì)算器,而且你可以清除這些結(jié)果。下面是其實(shí)現(xiàn)代碼:
package calc; public class Calculator { private static int result; //存儲(chǔ)結(jié)果的靜態(tài)變量 public void add(int n) { result = result + n; } public void substract(int n) { result = result - 1; //錯(cuò)誤:應(yīng)該是"result = result - n" } public void multiply(int n) {} //還沒實(shí)現(xiàn) public void divide(int n) { result = result / n; } public void square(int n) { result = n * n; } public void squareRoot(int n) { for (; ;) ; //錯(cuò)誤:無限循環(huán) } public void clear() { //清除結(jié)果 result = 0; } public void switchOn() { //打開屏幕,顯示"hello",并報(bào)警 result = 0; //實(shí)現(xiàn)其它的計(jì)算器功能 } public void switchOff() { } //顯示"bye bye",報(bào)警,并關(guān)閉屏幕 public int getResult() { return result; } } |
二、 遷移一個(gè)測試類
現(xiàn)在,我將把一個(gè)已經(jīng)使用JUnit 3.8編寫成的簡單的測試類遷移到JUnit 4。這個(gè)類有一些缺陷:它沒有測試所有的業(yè)務(wù)方法,而且看上去在testDivide方法中還存在一個(gè)錯(cuò)誤(8/2不等于5)。因?yàn)檫€沒有實(shí)現(xiàn)乘法運(yùn)算功能,所以對其測試將被忽略。
下面,我們把兩個(gè)版本的框架之間的差別以粗體顯示出現(xiàn)于表格1中。
表格1.分別以JUnit 3.8和JUnit 4實(shí)現(xiàn)的CaculatorTest。
JUnit 3.8
package junit3; import calc.Calculator; import junit.framework.TestCase; public class CalculatorTest extends TestCase { private static Calculator calculator = new Calculator(); @Override protected void setUp() { calculator.clear(); } public void testAdd() { calculator.add(1); calculator.add(1); assertEquals(calculator.getResult(), 2); } public void testSubtract() { calculator.add(10); calculator.subtract(2); assertEquals(calculator.getResult(), 8); } public void testDivide() { calculator.add(8); calculator.divide(2); assert calculator.getResult() == 5; } public void testDivideByZero() { try { calculator.divide(0); fail(); } catch (ArithmeticException e) { } } public void notReadyYetTestMultiply() { calculator.add(10); calculator.multiply(10); assertEquals(calculator.getResult(), 100); } } |
JUnit 4
package JUnit 4; import calc.Calculator; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; public class CalculatorTest { private static Calculator calculator = new Calculator(); @Before public void clearCalculator() { calculator.clear(); } @Test public void add() { calculator.add(1); calculator.add(1); assertEquals(calculator.getResult(), 2); } @Test public void subtract() { calculator.add(10); calculator.subtract(2); assertEquals(calculator.getResult(), 8); } @Test public void divide() { calculator.add(8); calculator.divide(2); assert calculator.getResult() == 5; } @Test(expected = ArithmeticException.class) public void divideByZero() { calculator.divide(0); } @Ignore("not ready yet") @Test public void multiply() { calculator.add(10); calculator.multiply(10); assertEquals(calculator.getResult(), 100); } } |
首先,你可以看到,JUnit 4使用org.junit.*包而JUnit 3.8使用的是junit.framework.*。當(dāng)然,為了向后兼容性起見,JUnit 4jar文件發(fā)行中加入了這兩種包。
四、 繼承
在中,測試類不必再擴(kuò)展junit.framework.TestCase;事實(shí)上,它們不必須擴(kuò)展任何內(nèi)容。但是,JUnit 4中使用的是注解。為了以一個(gè)測試用例方式執(zhí)行,一個(gè)JUnit 4類中至少需要一個(gè)@Test注解。例如,如果你僅使用@Before和@After注解而沒有至少提供一個(gè)@Test方法來編寫一個(gè)類,那么,當(dāng)你試圖 執(zhí)行它時(shí)將得到一個(gè)錯(cuò)誤:
java.lang.Exception: No runnable methods.
五、 斷言(Assert)方法
因?yàn)樵贘Unit 4中一個(gè)測試類并不繼承自TestCase(在JUnit 3.8中,這個(gè)類中定義了assertEquals()方法),所以你必須使用前綴語法(舉例來說,Assert.assertEquals())或者 (由于JDK5.0)靜態(tài)地導(dǎo)入Assert類。這樣以來,你就可以完全象以前一樣使用assertEquals方法(舉例來說, assertEquals())。
另外,在JUnit 4中,還引入了兩個(gè)新的斷言方法,它們專門用于數(shù)組對象的比較。如果兩個(gè)數(shù)組包含的元素都相等,那么這兩個(gè)數(shù)組就是相等的。
public static void assertEquals(String message, Object[] expecteds, Object[] actuals); public static void assertEquals(Object[] expecteds, Object[] actuals); |
由于JDK 5.0的自動(dòng)裝箱機(jī)制的出現(xiàn),原先的12個(gè)assertEquals方法全部去掉了。例如,原先JUnit 3.8中的assertEquals(long,long)方法在JUnit 4中要使用assertEquals(Object,Object)。對于assertEquals(byte,byte)、assertEquals (int,int)等也是這樣。這種改進(jìn)將有助于避免反模式。
在JUnit 4中,新集成了一個(gè)assert關(guān)鍵字(見我們的例子中的divide()方法)。你可以象使用assertEquals方法一樣來使用它,因?yàn)樗鼈兌紥?出相同的異常(java.lang.AssertionError)。JUnit 3.8的assertEquals將拋出一個(gè)junit.framework.AssertionFailedError。注意,當(dāng)使用assert時(shí), 你必須指定Java的"-ea"參數(shù);否則,斷言將被忽略。
六、 預(yù)設(shè)環(huán)境(Fixture)
Fixture是在測試期間初始化和釋放任何普通對象的方法。在JUnit 3.8中,你要使用setUp()來實(shí)現(xiàn)運(yùn)行每一個(gè)測試前的初始化工作,然后使用tearDown()來進(jìn)行每個(gè)測試后的清理。這兩個(gè)方法在 TestCase類中都得到重載,因此都被唯一定義。注意,我在這個(gè)Setup方法使用的是Java5.0內(nèi)置的@Override注解-這個(gè)注解指示該 方法聲明要重載在超類中的方法聲明。在JUnit 4中,則代之使用的是@Before和@After注解;而且,可以以任何命名(在我們的例子中是clearCalculator())來調(diào)用這些方法。 在本文后面,我將更多地解釋這些注解。
七、 測試
JUnit 3.8通過分析它的簽名來識(shí)別一個(gè)測試方法:方法名必須以"test"為前綴,它必須返回void,而且它必須沒有任何參數(shù)(舉例來說, publicvoidtestDivide())。一個(gè)不遵循這個(gè)命名約定的測試方法將被框架簡單地忽略,而且不拋出任何異常(指示發(fā)生了一個(gè)錯(cuò)誤)。
JUnit 4不使用與JUnit 3.8相同的約定。一個(gè)測試方法不必以'test'為前綴,但是要使用@Test注解。但是,正如在前一個(gè)框架中一樣,一個(gè)測試方法也必須返回void并 且是無參數(shù)的。在JUnit 4中,可以在運(yùn)行時(shí)刻控制這個(gè)要求,并且不符合要求的話會(huì)拋出一個(gè)異常:
java.lang.Exception: Method xxx should have no parameters
java.lang.Exception: Method xxx should be void
@Test注解支持可選參數(shù)。它聲明一個(gè)測試方法應(yīng)該拋出一個(gè)異常。如果它不拋出或者如果它拋出一個(gè)與事先聲明的不同的異常,那么該測試失敗。在我們的例子中,一個(gè)整數(shù)被零除應(yīng)該引發(fā)一個(gè)ArithmeticException異常。
八、 忽略一個(gè)測試
記住,不能執(zhí)行多個(gè)方法。然而,如果你不想讓測試失敗的話,你可以僅僅忽略它。那么,在JUnit 3.8中,我們是如何實(shí)現(xiàn)臨時(shí)禁止一個(gè)測試的呢?方法是:通過注釋掉它或者改變命名約定,這樣測試運(yùn)行機(jī)就無法找到它。在我的例子中,我使用了方法名 notReadyYetTestMultiply()。它沒有以"test"開頭,所以它不會(huì)被識(shí)別出來。現(xiàn)在的問題是,在成百上千的測試中間,你可能記 不住重命名這個(gè)方法。
在JUnit 4中,為了忽略一個(gè)測試,你可以注釋掉一個(gè)方法或者刪除@Test注解(你不能再改變命名約定,否則將拋出一個(gè)異常)。然而,該問題將保留:該運(yùn)行機(jī)將不 報(bào)告這樣一個(gè)測試。現(xiàn)在,你可以把@Ignore注解添加到@Test的前面或者后面。測試運(yùn)行機(jī)將報(bào)告被忽略的測試的個(gè)數(shù),以及運(yùn)行的測試的數(shù)目和運(yùn)行 失敗的測試數(shù)目。注意,@Ignore使用一個(gè)可選參數(shù)(一個(gè)String),如果你想記錄為什么一個(gè)測試被忽略的話。
九、 運(yùn)行測試
在JUnit 3.8中,你可以選擇使用若干運(yùn)行機(jī):文本型,AWT或者Swing。JUnit 4僅僅使用文本測試運(yùn)行機(jī)。注意,JUnit 4不會(huì)顯示任何綠色條來通知你測試成功了。如果你想看到任何類型的綠色的話,那么你可能需要使用JUnit擴(kuò)展或一種集成了JUnit的IDE(例如 IDEA或者Eclipse)。
首先,我想使用老式但好用的junit.textui.TestRunner來運(yùn)行該JUnit 3.8測試類(考慮到使用assert關(guān)鍵字,我使用了-ea參數(shù))。
java -ea junit.textui.TestRunner junit3.CalculatorTest
..F.E. There was 1 error: 1) testDivide(junit3.CalculatorTest)java.lang.AssertionError at junit3.CalculatorTest.testDivide(CalculatorTest.java:33) There was 1 failure: 1) testSubtract(junit3.CalculatorTest)junit.framework.AssertionFailedError: expected:<9> but was:<8> at junit3.CalculatorTest.testSubtract(CalculatorTest.java:27) FAILURES!!! Tests run: 4, Failures: 1, Errors: 1 |
TestDivide產(chǎn)生一個(gè)錯(cuò)誤,因?yàn)閿嘌源_定了8/2不等于5。TestSubstract產(chǎn)生一個(gè)失敗,因?yàn)?0-2應(yīng)該等于8,但是在這個(gè)實(shí)現(xiàn)中存在一個(gè)錯(cuò)誤:它返回9。
現(xiàn)在,我使用新的org.junit.runner.JUnitCore運(yùn)行機(jī)來運(yùn)行這兩個(gè)類。注意,它能執(zhí)行JUnit 4和JUnit 3.8測試,甚至是這二者的結(jié)合。
java -ea org.junit.runner.JUnitCore junit3.CalculatorTest JUnit version 4.1 ..E.E. There were 2 failures: 1) testSubtract(junit3.CalculatorTest) junit.framework.AssertionFailedError: expected:<9> but was:<8> at junit.framework.Assert.fail(Assert.java:47) 2) testDivide(junit3.CalculatorTest) java.lang.AssertionError at junit3.CalculatorTest.testDivide(CalculatorTest.java:33) FAILURES!!! Tests run: 4, Failures: 2 *** java -ea org.junit.runner.JUnitCore JUnit 4.CalculatorTest JUnit version 4.1 ...E.EI There were 2 failures: 1) subtract(JUnit 4.CalculatorTest) java.lang.AssertionError: expected:<9> but was:<8> at org.junit.Assert.fail(Assert.java:69) 2) divide(JUnit 4.CalculatorTest) java.lang.AssertionError at JUnit 4.CalculatorTest.divide(CalculatorTest.java:40) FAILURES!!! Tests run: 4, Failures: 2 |
第一個(gè)非常明顯的區(qū)別是,JUnit版本號(hào)被顯示于控制臺(tái)中(4.1)。第二個(gè)區(qū)別是,JUnit 3.8區(qū)分失敗和錯(cuò)誤;JUnit 4則僅使用失敗進(jìn)行簡化。一個(gè)新奇的地方是,字母"I",它顯示一個(gè)測試被忽略。