利用Junit4進行程式模組的測試,迴歸測試

迎面有風吹過來發表於2015-05-04

①在你的工程裡匯入JUnit4的包

②右擊建立JUnit測試類,在測試類中編寫測試程式碼即可。

JUnit 目前需要掌握的有一下幾點:

Fixture系列:BeforeClass,AfterClass,Before,After

普通測試:Ignore(忽視),Text(測試),Test(timeout = 1000)(限時測試),Test(expected = ArithmeticException.class)(異常測試)

特殊測試:批量引數測試,打包測試

③Fixture系列與普通測試例程式碼

[java] view plain

copy

1. package com.mikuscallion.main;  

2.   

3. import static org.junit.Assert.*;  

4.   

5. import org.junit.After;  

6. import org.junit.AfterClass;  

7. import org.junit.Before;  

8. import org.junit.BeforeClass;  

9. import org.junit.Ignore;  

10. import org.junit.Test;  

11. public class CalculatorTest {  

12.     //測試物件  

13.     Calculator calculator =new Calculator();  

14.     //Fixture  

15.     @BeforeClass  

16.     public static void beforeClass(){  

17.         System.out.println("在測試類初始化時,呼叫一次");  

18.     }  

19.     @AfterClass  

20.     public static void afterClass(){  

21.         System.out.println("在測試類執行結束時,呼叫一次");  

22.     }  

23.     @Before  

24.     public void before(){  

25.         System.out.println("執行任何測試程式碼前呼叫");  

26.         calculator.clear();  

27.     }  

28.     @After  

29.     public void after(){  

30.         System.out.println("執行任何測試程式碼後呼叫");  

31.     }  

32.     //Test---------------------------------------------------------------------------------  

33.     @Ignore  

34.     //忽視測試  

35.     public void testMultiply() {  

36.         calculator.multiply(1);  

37.         calculator.multiply(5);  

38.         //斷言結果  

39.         assertEquals(5, calculator.getResult());  

40.     }  

41.     @Test(timeout = 1000)  

42.     //限時測試   

43.     public void limitTimeTest(){  

44.         calculator.squareRoot(5);  

45.     }  

46.     @Test(expected = ArithmeticException.class)  

47.     //異常測試  

48.     public void exceptTest(){  

49.         calculator.divide(0);  

50.     }  

51.     @Test  

52.     public void testAdd() {  

53.         calculator.add(1);  

54.         calculator.add(2);  

55.         assertEquals(3, calculator.getResult());  

56.     }  

57.     @Test  

58.     public void testSubstract() {  

59.         calculator.add(10);  

60.         calculator.substract(2);  

61.         assertEquals(8, calculator.getResult());  

62.     }  

63.     @Test  

64.     public void testDivide() {  

65.         calculator.add(8);  

66.         calculator.divide(2);  

67.         assertEquals(4, calculator.getResult());  

68.     }  

69.       

70. }  

 

④引數測試程式碼

[java] view plain

copy

1. package com.mikuscallion.main;  

2.   

3. import static org.junit.Assert.*;  

4.   

5. import java.util.Arrays;  

6. import java.util.Collection;  

7.   

8. import org.junit.Test;  

9. import org.junit.runner.RunWith;  

10. import org.junit.runners.Parameterized;  

11. import org.junit.runners.Parameterized.Parameters;  

12.   

13. @RunWith(Parameterized.class)  

14. public class SquareTest {  

15.     public Calculator calculator =new Calculator();  

16.     public int param;  

17.     public int result;  

18.       

19.     @Parameters  

20.     public static Collection data(){  

21.         //注意這種寫法  

22.         return Arrays.asList(new Object[][]{  

23.                 {2, 4},  

24.                 {0, 0},  

25.                 {-3, 9},  

26.         });  

27.     }  

28.     public SquareTest(int param, int result){  

29.         this.param = param;  

30.         this.result = result;  

31.     }  

32.     @Test  

33.     public void testSquare() {  

34.         calculator.square(param);  

35.         assertEquals(result, calculator.getResult());  

36.     }  

37. }  

⑤批量測試程式碼

[java] view plain

copy

1. package com.mikuscallion.main;  

2.   

3.   

4. import org.junit.runner.RunWith;  

5. import org.junit.runners.Suite;  

6. import org.junit.runners.Suite.SuiteClasses;  

7. @SuiteClasses({  

8.     SquareTest.class,  

9.     CalculatorTest.class,  

10. })  

11. @RunWith(Suite.class)  

12. public class SuiteTest {  

13.       

14. }  

⑥被測試類程式碼

[java] view plain

copy

1. package com.mikuscallion.main;  

2. public class Calculator {  

3.           

4.         private static int result; // 靜態變數,用於儲存執行結果  

5.           

6.         public void add(int n){  

7.             result = result + n;  

8.         }  

9.         public void substract(int n){  

10.             result = result - n; //Bug: 正確的應該是 result =result-n  

11.         }  

12.         public void multiply(int n){  

13.         } // 此方法尚未寫好  

14.         public void divide(int n){  

15.         result = result / n;  

16.         }  

17.           

18.         //  

19.         public void square(int n){  

20.             result = n * n;  

21.         }  

22.         public void squareRoot(int n){  

23.             for (; ;) ; //Bug : 死迴圈  

24.         }  

25.         public void clear(){ // 將結果清零  

26.             result = 0;  

27.         }  

28.         public int getResult(){  

29.             return result;  

30.         }  

31. }

首先新建一個專案叫JUnit_Test,我們編寫一個Calculator類,這是一個能夠簡單實現加減乘除、平方、開方的計算器類,然後對這些功能進行單元測試。這個類並不是很完美,我們故意保留了一些Bug用於演示,這些Bug在註釋中都有說明。該類程式碼如下:

package andycpp;

 

public class Calculator ...{

private static int result; // 靜態變數,用於儲存執行結果

public void add(int n) ...{

result = result + n;

}

public void substract(int n) ...{

result = result - 1; //Bug: 正確的應該是 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 (; ;) ; //Bug : 死迴圈

}

public void clear() ...{ // 將結果清零

result = 0;

}

public int getResult() ...{

return result;

}

 

 

 

第二步,將JUnit4單元測試包引入這個專案:在該專案上點右鍵,點“屬性”,如圖:

 

在彈出的屬性視窗中,首先在左邊選擇“Java Build Path”,然後到右上選擇“Libraries”標籤,之後在最右邊點選“Add Library…”按鈕,如下圖所示:

 

然後在新彈出的對話方塊中選擇JUnit4並點選確定,如上圖所示,JUnit4軟體包就被包含進我們這個專案了。

第三步,生成JUnit測試框架:在Eclipse的Package Explorer中用右鍵點選該類彈出選單,選擇“New à JUnit Test Case”。如下圖所示:

 

在彈出的對話方塊中,進行相應的選擇,如下圖所示:

 

點選“下一步”後,系統會自動列出你這個類中包含的方法,選擇你要進行測試的方法。此例中,我們僅對“加、減、乘、除”四個方法進行測試。如下圖所示:

在Eclipse中使用JUnit4進行單元測試(初級篇) - 蛋蛋 - Monicas  fossa

之後系統會自動生成一個新類CalculatorTest,裡面包含一些空的測試用例。你只需要將這些測試用例稍作修改即可使用。完整的CalculatorTest程式碼如下:

 

package andycpp;

 

import static org.junit.Assert.*;

import org.junit.Before;

import org.junit.Ignore;

import org.junit.Test;

 

public class CalculatorTest ...{

 

private static Calculator calculator = new Calculator();

 

@Before

public void setUp() throws Exception ...{

calculator.clear();

}

 

@Test

public void testAdd() ...{

calculator.add(2);

calculator.add(3);

assertEquals(5, calculator.getResult());

}

 

@Test

public void testSubstract() ...{

calculator.add(10);

calculator.substract(2);

assertEquals(8, calculator.getResult());

}

 

@Ignore("Multiply() Not yet implemented")

@Test

public void testMultiply() ...{

}

 

@Test

public void testDivide() ...{

calculator.add(8);

calculator.divide(2);

assertEquals(4, calculator.getResult());

}

}

 

 

第四步,執行測試程式碼:按照上述程式碼修改完畢後,我們在CalculatorTest類上點右鍵,選擇“Run As à JUnit Test”來執行我們的測試,如下圖所示:

 

執行結果如下:

 

 

進度條是紅顏色表示發現錯誤,具體的測試結果在進度條上面有表示“共進行了4個測試,其中1個測試被忽略,一個測試失敗”

 

在測試類中用到了JUnit4框架,自然要把相應地Package包含進來。最主要地一個Package就是org.junit.*。把它包含進來之後,絕大部分功能就有了。還有一句話也非常地重要“import static org.junit.Assert.*;”,我們在測試的時候使用的一系列assertEquals方法就來自這個包。大家注意一下,這是一個靜態包含(static),是JDK5中新增添的一個功能。也就是說,assertEquals是Assert類中的一系列的靜態方法,一般的使用方式是Assert. assertEquals(),但是使用了靜態包含後,前面的類名就可以省略了,使用起來更加的方便。

二、     測試類的宣告

大家注意到,我們的測試類是一個獨立的類,沒有任何父類。測試類的名字也可以任意命名,沒有任何侷限性。所以我們不能通過類的宣告來判斷它是不是一個測試類,它與普通類的區別在於它內部的方法的宣告,我們接著會講到。

三、     建立一個待測試的物件。

你要測試哪個類,那麼你首先就要建立一個該類的物件。正如上一篇文章中的程式碼:

private static Calculator calculator = new Calculator();

 

為了測試Calculator類,我們必須建立一個calculator物件。

四、     測試方法的宣告

在測試類中,並不是每一個方法都是用於測試的,你必須使用“標註”來明確表明哪些是測試方法。“標註”也是JDK5的一個新特性,用在此處非常恰當。我們可以看到,在某些方法的前有@Before、@Test、@Ignore等字樣,這些就是標註,以一個“@”作為開頭。這些標註都是JUnit4自定義的,熟練掌握這些標註的含義非常重要。

五、     編寫一個簡單的測試方法。

首先,你要在方法的前面使用@Test標註,以表明這是一個測試方法。對於方法的宣告也有如下要求:名字可以隨便取,沒有任何限制,但是返回值必須為void,而且不能有任何引數。如果違反這些規定,會在執行時丟擲一個異常。至於方法內該寫些什麼,那就要看你需要測試些什麼了。比如:

@Test

 

public void testAdd() ...{

 

calculator.add(2);

 

calculator.add(3);

 

assertEquals(5, calculator.getResult());

 

}

 

 

我們想測試一下“加法”功能時候正確,就在測試方法中呼叫幾次add函式,初始值為0,先加2,再加3,我們期待的結果應該是5。如果最終實際結果也是5,則說明add方法是正確的,反之說明它是錯的。assertEquals(5, calculator.getResult());就是來判斷期待結果和實際結果是否相等,第一個引數填寫期待結果,第二個引數填寫實際結果,也就是通過計算得到的結果。這樣寫好之後,JUnit會自動進行測試並把測試結果反饋給使用者。

六、     忽略測試某些尚未完成的方法。

如果你在寫程式前做了很好的規劃,那麼哪些方法是什麼功能都應該實現定下來。因此,即使該方法尚未完成,他的具體功能也是確定的,這也就意味著你可以為他編寫測試用例。但是,如果你已經把該方法的測試用例寫完,但該方法尚未完成,那麼測試的時候一定是“失敗”。這種失敗和真正的失敗是有區別的,因此JUnit提供了一種方法來區別他們,那就是在這種測試函式的前面加上@Ignore標註,這個標註的含義就是“某些方法尚未完成,暫不參與此次測試”。這樣的話測試結果就會提示你有幾個測試被忽略,而不是失敗。一旦你完成了相應函式,只需要把@Ignore標註刪去,就可以進行正常的測試。

七、     Fixture(暫且翻譯為“固定程式碼段”)

Fixture的含義就是“在某些階段必然被呼叫的程式碼”。比如我們上面的測試,由於只宣告瞭一個Calculator物件,他的初始值是0,但是測試完加法操作後,他的值就不是0了;接下來測試減法操作,就必然要考慮上次加法操作的結果。這絕對是一個很糟糕的設計!我們非常希望每一個測試都是獨立的,相互之間沒有任何耦合度。因此,我們就很有必要在執行每一個測試之前,對Calculator物件進行一個“復原”操作,以消除其他測試造成的影響。因此,“在任何一個測試執行之前必須執行的程式碼”就是一個Fixture,我們用@Before來標註它,如前面例子所示:

@Before

 

public void setUp() throws Exception ...{

 

calculator.clear();

 

}

 

這裡不在需要@Test標註,因為這不是一個test,而是一個Fixture。同理,如果“在任何測試執行之後需要進行的收尾工作”也是一個Fixture,使用@After來標註。由於本例比較簡單,沒有用到此功能。

 

有一個類是負責對大檔案(超過500兆)進行讀寫,他的每一個方法都是對檔案進行操作。換句話說,在呼叫每一個方法之前,我們都要開啟一個大檔案並讀入檔案內容,這絕對是一個非常耗費時間的操作。如果我們使用@Before和@After,那麼每次測試都要讀取一次檔案,效率及其低下。這裡我們所希望的是在所有測試一開始讀一次檔案,所有測試結束之後釋放檔案,而不是每次測試都讀檔案。JUnit的作者顯然也考慮到了這個問題,它給出了@BeforeClass 和 @AfterClass兩個Fixture來幫我們實現這個功能。從名字上就可以看出,用這兩個Fixture標註的函式,只在測試用例初始化時執行@BeforeClass方法,當所有測試執行完畢之後,執行@AfterClass進行收尾工作。在這裡要注意一下,每個測試類只能有一個方法被標註為@BeforeClass 或 @AfterClass,並且該方法必須是Public和Static的。

 

二、     限時測試。

還記得我在初級篇中給出的例子嗎,那個求平方根的函式有Bug,是個死迴圈:

 

    public void squareRoot(int n) ...{

        for (; ;) ;                 //Bug : 死迴圈

    }


 

 

如果測試的時候遇到死迴圈,你的臉上絕對不會露出笑容。因此,對於那些邏輯很複雜,迴圈巢狀比較深的程式,很有可能出現死迴圈,因此一定要採取一些預防措施。限時測試是一個很好的解決方案。我們給這些測試函式設定一個執行時間,超過了這個時間,他們就會被系統強行終止,並且系統還會向你彙報該函式結束的原因是因為超時,這樣你就可以發現這些Bug了。要實現這一功能,只需要給@Test標註加一個引數即可,程式碼如下:

 

 

    @Test(timeout = 1000)

    public void squareRoot() ...{

        calculator.squareRoot(4);

        assertEquals(2, calculator.getResult());

    }

 

 

Timeout參數列明了你要設定的時間,單位為毫秒,因此1000就代表1秒。

三、     測試異常

JAVA中的異常處理也是一個重點,因此你經常會編寫一些需要丟擲異常的函式。那麼,如果你覺得一個函式應該丟擲異常,但是它沒丟擲,這算不算Bug呢?這當然是Bug,並JUnit也考慮到了這一點,來幫助我們找到這種Bug。例如,我們寫的計算器類有除法功能,如果除數是一個0,那麼必然要丟擲“除0異常”。因此,我們很有必要對這些進行測試。程式碼如下:

 

 

 @Test(expected = ArithmeticException.class)

  public void divideByZero() ...{

calculator.divide(0); 

  }


 

 

 

如上述程式碼所示,我們需要使用

@Test標註的expected屬性,將我們要檢驗的異常傳遞給他,這樣JUnit框架就能自動幫我們檢測是否丟擲了我們指定的異常。

四、     Runner (執行器)

大家有沒有想過這個問題,當你把測試程式碼提交給JUnit框架後,框架如何來執行你的程式碼呢?答案就是——Runner。在JUnit中有很多個Runner,他們負責呼叫你的測試程式碼,每一個Runner都有各自的特殊功能,你要根據需要選擇不同的Runner來執行你的測試程式碼。可能你會覺得奇怪,前面我們寫了那麼多測試,並沒有明確指定一個Runner啊?這是因為JUnit中有一個預設Runner,如果你沒有指定,那麼系統自動使用預設Runner來執行你的程式碼。換句話說,下面兩段程式碼含義是完全一樣的:

 

 

import org.junit.internal.runners.TestClassRunner;

import org.junit.runner.RunWith;

//使用了系統預設的TestClassRunner,與下面程式碼完全一樣

public class CalculatorTest ...{

...

}

@RunWith(TestClassRunner.class)

public class CalculatorTest ...{

...

}


 

 

從上述例子可以看出,要想指定一個Runner,需要使用@RunWith標註,並且把你所指定的Runner作為引數傳遞給它。另外一個要注意的是,@RunWith是用來修飾類的,而不是用來修飾函式的。只要對一個類指定了Runner,那麼這個類中的所有函式都被這個Runner來呼叫。最後,不要忘了包含相應的Package哦,上面的例子對這一點寫的很清楚了。接下來,我會向你們展示其他Runner的特有功能。

五、     引數化測試。

你可能遇到過這樣的函式,它的引數有許多特殊值,或者說他的引數分為很多個區域。比如,一個對考試分數進行評價的函式,返回值分別為“優秀,良好,一般,及格,不及格”,因此你在編寫測試的時候,至少要寫5個測試,把這5中情況都包含了,這確實是一件很麻煩的事情。我們還使用我們先前的例子,測試一下“計算一個數的平方”這個函式,暫且分三類:正數、0、負數。測試程式碼如下:

 

 

import org.junit.AfterClass;

import org.junit.Before;

import org.junit.BeforeClass;

import org.junit.Test;

import static org.junit.Assert.*;

public class AdvancedTest ...{

private static Calculator calculator = new Calculator();

    @Before

public void clearCalculator() ...{

        calculator.clear();

    }

    @Test

    public void square1() ...{

        calculator.square(2);

        assertEquals(4, calculator.getResult());

    }

    @Test

    public void square2() ...{

        calculator.square(0);

        assertEquals(0, calculator.getResult());

    }

    @Test

    public void square3() ...{

        calculator.square(-3);

        assertEquals(9, calculator.getResult());

    }

}


 

 

為了簡化類似的測試,JUnit4提出了“引數化測試”的概念,只寫一個測試函式,把這若干種情況作為引數傳遞進去,一次性的完成測試。程式碼如下:

 

 

import static org.junit.Assert.assertEquals;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.junit.runners.Parameterized;

import org.junit.runners.Parameterized.Parameters;

import java.util.Arrays;

import java.util.Collection;

@RunWith(Parameterized.class)

public class SquareTest ...{

    private static Calculator calculator = new Calculator();

    private int param;

    private int result;

    @Parameters

    public static Collection data() ...{

        return Arrays.asList(new Object[][]...{

                ...{2, 4},

                ...{0, 0},

                ...{-3, 9},

        });

    }

//建構函式,對變數進行初始化

    public SquareTest(int param, int result) ...{

        this.param = param;

        this.result = result;

    }

    @Test

    public void square() ...{

       calculator.square(param);

        assertEquals(result, calculator.getResult());

    }

}


 

 

下面我們對上述程式碼進行分析。首先,你要為這種測試專門生成一個新的類,而不能與其他測試共用同一個類,此例中我們定義了一個SquareTest類。然後,你要為這個類指定一個Runner,而不能使用預設的Runner了,因為特殊的功能要用特殊的Runner嘛。@RunWith(Parameterized.class)這條語句就是為這個類指定了一個ParameterizedRunner。第二步,定義一個待測試的類,並且定義兩個變數,一個用於存放引數,一個用於存放期待的結果。接下來,定義測試資料的集合,也就是上述的data()方法,該方法可以任意命名,但是必須使用@Parameters標註進行修飾。這個方法的框架就不予解釋了,大家只需要注意其中的資料,是一個二維陣列,資料兩兩一組,每組中的這兩個資料,一個是引數,一個是你預期的結果。比如我們的第一組{2, 4},2就是引數,4就是預期的結果。這兩個資料的順序無所謂,誰前誰後都可以。之後是建構函式,其功能就是對先前定義的兩個引數進行初始化。在這裡你可要注意一下引數的順序了,要和上面的資料集合的順序保持一致。如果前面的順序是{引數,期待的結果},那麼你建構函式的順序也要是“建構函式(引數, 期待的結果)”,反之亦然。最後就是寫一個簡單的測試例了,和前面介紹過的寫法完全一樣,在此就不多說。

六、     打包測試。

通過前面的介紹我們可以感覺到,在一個專案中,只寫一個測試類是不可能的,我們會寫出很多很多個測試類。可是這些測試類必須一個一個的執行,也是比較麻煩的事情。鑑於此,JUnit為我們提供了打包測試的功能,將所有需要執行的測試類集中起來,一次性的執行完畢,大大的方便了我們的測試工作。具體程式碼如下:

 

import org.junit.runner.RunWith;

import org.junit.runners.Suite;

@RunWith(Suite.class)

@Suite.SuiteClasses(...{

        CalculatorTest.class,

        SquareTest.class

        })

public class AllCalculatorTests ...{

}


 

 

大家可以看到,這個功能也需要使用一個特殊的Runner,因此我們需要向@RunWith標註傳遞一個引數Suite.class。同時,我們還需要另外一個標註@Suite.SuiteClasses,來表明這個類是一個打包測試類。我們把需要打包的類作為引數傳遞給該標註就可以了。有了這兩個標註之後,就已經完整的表達了所有的含義,因此下面的類已經無關緊要,隨便起一個類名,內容全部為空既可。

github地址:https://github.com/wangyayao415/Junit4

相關文章