VOOZH about

URL: https://read01.com/a0DLRL.html

⇱ Eclipse使用JUnit4進行單元測試 - 壹讀


Sunday, Apr 12, 2026

Eclipse使用JUnit4進行單元測試

2015/04/23 來源:CSDN博客

一,我們先謝一個Demo從頭來一遍再具體說明。

首先新建一個項目叫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; } }
👁 Image
...


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

👁 Image
...
然後在新彈出的對話框中選擇JUnit4並點擊確定,如上圖所示,JUnit4軟體包就被包含進我們這個項目了。

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

👁 Image
...
在彈出的對話框中,進行相應的選擇,如下圖所示:

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

👁 Image
...

之後系統會自動生成一個新類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」來運行我們的測試,如下圖所示:
👁 Image
...
👁 Image
...

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

錯誤表示我們的代碼有誤,我們修改了Calculate中的substract的方法再右鍵RUN一下後發現就都通過了。

二,具體說明

1),包含必要地Package

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

2),測試類的聲明

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

3),創建一個待測試的對象。

你要測試哪個類,那麼你首先就要創建一個該類的對象。正如上一篇文章中的代碼:

private static Calculator calculator = new Calculator;

為了測試Calculator類,我們必須創建一個calculator對象。

4),測試方法的聲明

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

5),編寫一個簡單的測試方法。

首先,你要在方法的前面使用@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會自動進行測試並把測試結果反饋給用戶。

6),@Ignore 忽略測試某些尚未完成的方法。

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

7),Fixture(暫且翻譯為「固定代碼段」)

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

@Before public void setUp throws Exception { calculator.clear; }

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

三,提高

1),高級 Fixture

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

2),限時測試。

還記得我在初級篇中給出的例子嗎,那個求平方根的函數有 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 秒。

3), 測試異常

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

@Test(expected = ArithmeticException. class ) public void divideByZero { calculator.divide( 0 ); }

4),Runner ( 運行器 )

大家有沒有想過這個問題,當你把測試代碼提交給 JUnit 框架後,框架如何來運行你的代碼呢?答案就是—— Runner 。在 JUnit 中有很多個 Runner ,他們負責調用你的測試代碼,每一個 Runner 都有各自的特殊功能,你要根據需要選擇不同的Runner 來運行你的測試代碼。可能你會覺得奇怪,前面我們寫了那麼多測試,並沒有明確指定一個 Runner 啊?這是因為 JUnit 中有一個默認 Runner ,如果你沒有指定,那麼系統自動使用默認 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 { }
您可能感興趣
免責聲明:本文內容來源于CSDN博客,文章觀點不代表壹讀立場,如若侵犯到您的權益,或涉不實謠言,敬請向我們提出檢舉
最新文章 / 服務條款 / 私隱保護 / DMCA / 聯絡我們

壹讀/READ01.COM