JAVA—咖啡館

          ——歡迎訪問rogerfan的博客,常來《JAVA——咖啡館》坐坐,喝杯濃香的咖啡,彼此探討一下JAVA技術,交流工作經驗,分享JAVA帶來的快樂!本網站部分轉載文章,如果有版權問題請與我聯系。

          BlogJava 首頁 新隨筆 聯系 聚合 管理
            447 Posts :: 145 Stories :: 368 Comments :: 0 Trackbacks
          提要 本文將向你介紹如何實現從JUnit 3.8向JUnit 4的遷移。同時,還討論JUnit 4中的一些新特征,特別包括對注解的使用;最后,簡要介紹這個新版本的IDE集成現狀。

            一、 引言

            在本文開始,我將假定,你已經了解由Kent Beck和Erich Gamma發明的這個Java單元測試框架并因此而略過必要的簡介。所以,我將集中分析從JUnit 3.8到最新版本-JUnit 4的遷移過程以及其在IDE和Ant中的集成。

            JUnit 4是一種與其之前的版本完全不同的API,它根據Java 5.0中的新特征(注解,靜態導入等)構建而成。如你所見,JUnit 4更簡單、更豐富和更易于使用,而且它引入了更為靈活的初始化和清理工作,還有限時的和參數化測試用例。

             代碼實例最能說明問題。因此,在本文中,我將使用一個例子來展示不同的測試用例:一個計算器。該示例計算器很簡單,效率并不高,甚至還有一些錯誤;它僅 僅操作整數,并且把結果存儲在一個靜態變量中。Substract方法并不返回一個有效的結果,而且也沒有實現乘法運算,而且看上去在 squareRoot方法中還存在一個錯誤:無限循環。這些錯誤將幫助說明使用JUnit 4進行測試的有效性。你可以打開和關閉這個計算器,而且你可以清除這些結果。下面是其實現代碼:

          package calc;
          public class Calculator {
           private static int result; //存儲結果的靜態變量
           public void add(int n) {
            result = result + n;
           }
           public void substract(int n) {
            result = result - 1; //錯誤:應該是"result = result - n"
           }
           public void multiply(int n) {} //還沒實現
           public void divide(int n) {
            result = result / n;
           }
           public void square(int n) {
            result = n * n;
           }
           public void squareRoot(int n) {
            for (; ;) ; //錯誤:無限循環
           }
           public void clear() { //清除結果
            result = 0;
           }
           public void switchOn() { //打開屏幕,顯示"hello",并報警
            result = 0; //實現其它的計算器功能
           }
           public void switchOff() { } //顯示"bye bye",報警,并關閉屏幕
           public int getResult() {
            return result;
           }
          }

            二、 遷移一個測試類

            現在,我將把一個已經使用JUnit 3.8編寫成的簡單的測試類遷移到JUnit 4。這個類有一些缺陷:它沒有測試所有的業務方法,而且看上去在testDivide方法中還存在一個錯誤(8/2不等于5)。因為還沒有實現乘法運算功能,所以對其測試將被忽略。

            下面,我們把兩個版本的框架之間的差別以粗體顯示出現于表格1中。

            表格1.分別以JUnit 3.8和JUnit 4實現的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.*。當然,為了向后兼容性起見,JUnit 4jar文件發行中加入了這兩種包。

            四、 繼承

             在中,測試類不必再擴展junit.framework.TestCase;事實上,它們不必須擴展任何內容。但是,JUnit 4中使用的是注解。為了以一個測試用例方式執行,一個JUnit 4類中至少需要一個@Test注解。例如,如果你僅使用@Before和@After注解而沒有至少提供一個@Test方法來編寫一個類,那么,當你試圖 執行它時將得到一個錯誤:

          java.lang.Exception: No runnable methods.

            五、 斷言(Assert)方法

             因為在JUnit 4中一個測試類并不繼承自TestCase(在JUnit 3.8中,這個類中定義了assertEquals()方法),所以你必須使用前綴語法(舉例來說,Assert.assertEquals())或者 (由于JDK5.0)靜態地導入Assert類。這樣以來,你就可以完全象以前一樣使用assertEquals方法(舉例來說, assertEquals())。

            另外,在JUnit 4中,還引入了兩個新的斷言方法,它們專門用于數組對象的比較。如果兩個數組包含的元素都相等,那么這兩個數組就是相等的。

          public static void assertEquals(String message, Object[] expecteds, Object[] actuals);
          public static void assertEquals(Object[] expecteds, Object[] actuals);

             由于JDK 5.0的自動裝箱機制的出現,原先的12個assertEquals方法全部去掉了。例如,原先JUnit 3.8中的assertEquals(long,long)方法在JUnit 4中要使用assertEquals(Object,Object)。對于assertEquals(byte,byte)、assertEquals (int,int)等也是這樣。這種改進將有助于避免反模式。

            在JUnit 4中,新集成了一個assert關鍵字(見我們的例子中的divide()方法)。你可以象使用assertEquals方法一樣來使用它,因為它們都拋 出相同的異常(java.lang.AssertionError)。JUnit 3.8的assertEquals將拋出一個junit.framework.AssertionFailedError。注意,當使用assert時, 你必須指定Java的"-ea"參數;否則,斷言將被忽略。

            六、 預設環境(Fixture)

             Fixture是在測試期間初始化和釋放任何普通對象的方法。在JUnit 3.8中,你要使用setUp()來實現運行每一個測試前的初始化工作,然后使用tearDown()來進行每個測試后的清理。這兩個方法在 TestCase類中都得到重載,因此都被唯一定義。注意,我在這個Setup方法使用的是Java5.0內置的@Override注解-這個注解指示該 方法聲明要重載在超類中的方法聲明。在JUnit 4中,則代之使用的是@Before和@After注解;而且,可以以任何命名(在我們的例子中是clearCalculator())來調用這些方法。 在本文后面,我將更多地解釋這些注解。

            七、 測試

            JUnit 3.8通過分析它的簽名來識別一個測試方法:方法名必須以"test"為前綴,它必須返回void,而且它必須沒有任何參數(舉例來說, publicvoidtestDivide())。一個不遵循這個命名約定的測試方法將被框架簡單地忽略,而且不拋出任何異常(指示發生了一個錯誤)。
          JUnit 4不使用與JUnit 3.8相同的約定。一個測試方法不必以'test'為前綴,但是要使用@Test注解。但是,正如在前一個框架中一樣,一個測試方法也必須返回void并 且是無參數的。在JUnit 4中,可以在運行時刻控制這個要求,并且不符合要求的話會拋出一個異常:

          java.lang.Exception: Method xxx should have no parameters
          java.lang.Exception: Method xxx should be void

            @Test注解支持可選參數。它聲明一個測試方法應該拋出一個異常。如果它不拋出或者如果它拋出一個與事先聲明的不同的異常,那么該測試失敗。在我們的例子中,一個整數被零除應該引發一個ArithmeticException異常。

            八、 忽略一個測試

             記住,不能執行多個方法。然而,如果你不想讓測試失敗的話,你可以僅僅忽略它。那么,在JUnit 3.8中,我們是如何實現臨時禁止一個測試的呢?方法是:通過注釋掉它或者改變命名約定,這樣測試運行機就無法找到它。在我的例子中,我使用了方法名 notReadyYetTestMultiply()。它沒有以"test"開頭,所以它不會被識別出來。現在的問題是,在成百上千的測試中間,你可能記 不住重命名這個方法。

            在JUnit 4中,為了忽略一個測試,你可以注釋掉一個方法或者刪除@Test注解(你不能再改變命名約定,否則將拋出一個異常)。然而,該問題將保留:該運行機將不 報告這樣一個測試。現在,你可以把@Ignore注解添加到@Test的前面或者后面。測試運行機將報告被忽略的測試的個數,以及運行的測試的數目和運行 失敗的測試數目。注意,@Ignore使用一個可選參數(一個String),如果你想記錄為什么一個測試被忽略的話。

            九、 運行測試

             在JUnit 3.8中,你可以選擇使用若干運行機:文本型,AWT或者Swing。JUnit 4僅僅使用文本測試運行機。注意,JUnit 4不會顯示任何綠色條來通知你測試成功了。如果你想看到任何類型的綠色的話,那么你可能需要使用JUnit擴展或一種集成了JUnit的IDE(例如 IDEA或者Eclipse)。

            首先,我想使用老式但好用的junit.textui.TestRunner來運行該JUnit 3.8測試類(考慮到使用assert關鍵字,我使用了-ea參數)。
          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產生一個錯誤,因為斷言確定了8/2不等于5。TestSubstract產生一個失敗,因為10-2應該等于8,但是在這個實現中存在一個錯誤:它返回9。

            現在,我使用新的org.junit.runner.JUnitCore運行機來運行這兩個類。注意,它能執行JUnit 4和JUnit 3.8測試,甚至是這二者的結合。

          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

            第一個非常明顯的區別是,JUnit版本號被顯示于控制臺中(4.1)。第二個區別是,JUnit 3.8區分失敗和錯誤;JUnit 4則僅使用失敗進行簡化。一個新奇的地方是,字母"I",它顯示一個測試被忽略。
          posted on 2009-03-06 10:46 rogerfan 閱讀(342) 評論(0)  編輯  收藏 所屬分類: 【Java知識】【開源技術】
          主站蜘蛛池模板: 宣化县| 兴海县| 库尔勒市| 利津县| 景洪市| 九龙坡区| 乾安县| 牟定县| 教育| 吉林省| 大新县| 墨玉县| 平罗县| 衡山县| 江川县| 泽库县| 永胜县| 合阳县| 广南县| 鹤庆县| 资兴市| 兰考县| 彭泽县| 南阳市| 正阳县| 淄博市| 军事| 兴仁县| 新巴尔虎右旗| 昭通市| 富阳市| 乌兰察布市| 报价| 新乡县| 克什克腾旗| 曲麻莱县| 界首市| 遂平县| 南部县| 千阳县| 深水埗区|