天天看點

使用 JUnit 進行 Java 代碼的單元測試

  首先我們需要先下載下傳相應的 junit 相關的 jar 包,下載下傳的過程可以去 junit 的官方網站,也可以直接通過 maven 資源倉庫來完成,我這裡直接通過開源中國社群在國内的maven 鏡像下載下傳了 junit-4.8.2.jar 的版本,如下圖所示:

使用 JUnit 進行 Java 代碼的單元測試
使用 JUnit 進行 Java 代碼的單元測試

package net.oschina.bairrfhoinn.main;

public class calculator {

public void add(int n){

result += n;

}

public void substract(int n){

result -= n;

public void multiply(int n){

result *= n;

public void divide(int n){

result /= n;

public void square(int n){

result = n * n;

public int getreuslt(){

return result;

public void clear(){

result = 0;

private static int result;

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

  另外要注意的是,我們的測試類是壹個獨立的類,沒有任何父類。測試類的名字也可以任意命名,沒有任何局限性。是以我們不能通過類的聲明來判斷它是不是一個測試類,它與普通類的差別在于它内部的方法的聲明,我們接着會講到。在測試類中,并不是每壹個方法都是用于測試的,是以我們必須使用“注解”來明确表明哪些是測試方法。“注解”也是jdk5的壹個新特性,用在此處非常恰當。我們可以看到,在某些方法的前有@before、@test、@ignore等字樣,這些就是注解,以壹個“@”作為開頭。這些注解都是junit4自定義的,熟練掌握這些注解的含義,對于編寫恰當的測試類非常重要。

 接下來我們建立壹個測試類 calculatortest.java,代碼如下:

package net.oschina.bairrfhoinn.test;

import static org.junit.assert.*;

import org.junit.test;

import net.oschina.bairrfhoinn.main.calculator;

public class calculatortest {

private static calculator calculator = new calculator();

@test

public void testadd(){

calculator.add(7);

calculator.add(8);

assertequals(15, calculator.getreuslt());

  首先,我們要在方法的前面使用@test标注,以表明這是壹個測試方法。對于方法的聲明也有如下要求:名字可以随便取,沒有任何限制,但是傳回值必須為void,而且不能有任何參數。如果違反這些規定,會在運作時抛出壹個異常。至于方法内該寫些什麼,那就要看你需要測試些什麼了。比如上述代碼中,我們想測試壹下add()方法的功能是否正确,就在測試方法中調用幾次add函數,初始值為0,先加7,再加8,我們期待的結果應該是15。如果最終實際結果也是15,則說明add()方法是正确的,反之說明它是錯的。assertequals(15, calculator.getresult());就是用來判斷期待結果和實際結果是否相等,其中第壹個參數填寫期待結果,第二個參數填寫實際結果,也就是通過計算得到的結果。這樣寫好之後,junit 會自動進行測試并把測試結果回報給使用者。

  如果想運作它,可以在 eclipse 的資料總管中選擇該類檔案,然後點選右鍵,選擇 run as->junit test 即可看到運作結果如下圖所示:

使用 JUnit 進行 Java 代碼的單元測試

  使用@test 的屬性 ignore 指定測試時跳過這個方法

  如果你在寫程式前做了很好的規劃,那麼哪些方法是什麼功能都應該實作并且确定下來。是以,即使該方法尚未完成,他的具體功能也是确定的,這也就意味着你可以為他編寫測試用例。但是,如果你已經把該方法的測試用例寫完,但該方法尚未完成,那麼測試的時候無疑是“失敗”。這種失敗和真正的失敗是有差別的,是以 junit 提供了壹種方法來差別他們,那就是在這種測試函數的前面加上 @ignore 标注,這個标注的含義就是“某些方法尚未完成,暫不參與此次測試”。這樣的話測試結果就會提示你有幾個測試被忽略,而不是失敗。壹旦你完成了相應函數,隻需要把@ignore标注删去,就可以進行正常的測試。

  比如說上面的測試類 calculator.java 中,假設我們的 calculator 類的 multiply() 方法沒有實作,我們可以在測試類 calculatortest 中先寫如下測試代碼:

import org.junit.ignore;

... //此處代碼省略

@ignore("method square() not implemented, please test this later...")

public void testsquare(){

calculator.square(3);

assertequals(9, calculator.getreuslt());

  我們再運作壹次測試,會看到如下結果,從圖中可以很明顯的看出,方法testsquare() 上的 @ignore 注解已經生效了,運作時直接跳過了它,而方法testadd()仍然正常的運作并通過了測試。

使用 JUnit 進行 Java 代碼的單元測試

  使用注解 @before 和 @after 來完成前置工作和後置工作

  前置工作通常是指我們的測試方法在運作之前需要做的壹些準備工作,如資料庫的連接配接、檔案的加載、輸入資料的準備等需要在運作測試方法之前做的事情,都屬于前置工作;類似的,後置工作則是指測試方法在運作之後的壹些要做的事情,如釋放資料庫連接配接、輸入輸出流的關閉等;比如我們上面的測試,由于隻聲明了壹個 calculator 對象,他的初始值是0,但是測試完加法操作後,他的值就不是0了;接下來測試減法操作,就必然要考慮上次加法操作的結果。這絕對是壹個很糟糕的設計!我們非常希望每壹個測試方法都是獨立的,互相之間沒有任何耦合度。是以,我們就很有必要在執行每壹個測試方法之前,對calculator對象進行壹個“複原”操作,以消除其他測試造成的影響。是以,“在任何壹個測試方法執行之前必須執行的代碼”就是壹個前置工作,我們用注解 @before 來标注它,如下例子所示:

...

import org.junit.after;

import org.junit.before;

...//這裡省略部分代碼

@before

public void setup() throws exception {

calculator.clear();

@after

public void teardown() throws exception {

system.out.println("will do sth here...");

 另外要說的是,注解 @before 是定義在 org.junit.before 這個類中的,是以使用時需要将其引入我們的代碼中。這樣做了之後,每次我們運作測試方法時,junit 都會先運作 setup() 方法将 result 的值清零。不過要注意的是,這裡不再需要 @test 注解,因為這并不是壹個 test,隻是壹個前置工作。同理,如果“在任何測試執行之後需要進行的收尾工作,我們應該使用 @after 來标注,方法與它類似。由于本例比較簡單,不需要用到此功能,是以我們隻是簡單了給它添加了壹個 teardown() 方法并在收尾時列印壹句話到控制台,并且使用 @after 來注解這個方法。

  使用@beforeclass 和 @afterclass 來完成隻需要執行壹次的前置工作和後置工作

  上面我們提到了兩個注解 @before 和 @after ,我們來看看他們是否适合完成如下功能:有壹個類負責對大檔案(超過500 mb)進行讀寫,他的每壹個方法都是對檔案進行操作。換句話說,在調用每壹個方法之前,我們都要打開壹個大檔案并讀入檔案内容,這絕對是壹個非常耗費時的操作。如果我們使用 @before 和 @after ,那麼每次測試都要讀取壹次檔案,效率及其低下。是以我們希望的是,在所有測試壹開始讀壹次檔案,所有測試結束之後釋放檔案,而不是每次測試都讀檔案。junit的作者顯然也考慮到了這個問題,它給出了@beforeclass 和 @afterclass 兩個注解來幫我們實作這個功能。從名字上就可以看出,用這兩個注解标注的函數,隻在測試用例初始化時執行 @beforeclass 方法,當所有測試執行完畢之後,執行 @afterclass 進行收尾工作。在這裡要注意壹下,每個測試類隻能有壹個方法被标注為 @beforeclass 或 @afterclass,而且該方法必須是 public static 類型的。

  使用@test 的屬性 timeout 來完成限時測試,以檢測代碼中的死循環

  現在假設我們的 calculator 類中的 square() 方法是個死循環,那應該怎麼辦呢,比如說像下面這樣:

for(;;){}

  如果測試的時候遇到死循環,你的臉上絕對不會露出笑容的。是以,對于那些邏輯很複雜,循環嵌套比較深的、有可能出現死循環的程式,是以壹定要采取壹些預防措施。限時測試是壹個很好的解決方案。我們給這些測試函數設定壹個預期的執行時間,超過了這壹時間,他們就會被系統強行終止,并且系統還會向你彙報該函數結束的原因是因為逾時,這樣你就可以發現這些 bug 了。要實作這壹功能,隻需要給 @test 标注加壹個參數timeout即可,代碼如下:

@test(timeout=2000l)

public void testsquare() {

  timeout參數表明了你預計該方法運作的時長,機關為毫秒,是以2000就代表2秒。現在我們讓這個測試方法運作壹下,看看失敗時是什麼效果。

使用 JUnit 進行 Java 代碼的單元測試

  使用@test 的屬性expected來監控測試方法中可能會抛出的某些異常

  java中的異常處理也是壹個重點,是以你經常會編寫壹些需要抛出異常的函數。如果你覺得壹個函數應該抛出異常,但是它沒抛出,這算不算 bug 呢?這當然是bug,junit 也考慮到了這壹點,并且可以幫助我們找到這種 bug。例如,我們寫的電腦類有除法功能,如果除數是壹個0,那麼必然要抛出“除0異常”。是以,我們很有必要對這些進行測試。代碼如下:

@test(expected=java.lang.arithmeticexception.class)

public void testdivide(){

calculator.divide(0);

  如上述代碼所示,我們需要使用@test注解中的expected屬性,将我們要檢驗的異常(這裡是 java.lang.arithmeticexception)傳遞給他,這樣 junit 架構就能自動幫我們檢測是否抛出了我們指定的異常。

  指定 junit 運作測試用例時的 runner

  大家有沒有想過這個問題,當你把測試代碼送出給junit架構後,架構是如何來運作你的代碼的呢?答案就是runner。在junit中有很多個runner,他們負責調用你的測試代碼,每壹個runner都有其各自的特殊功能,你要根據需要選擇不同的runner來運作你的測試代碼。可能你會覺得奇怪,前面我們寫了那麼多測試,并沒有明确指定壹個runner啊?這是因為junit中有壹個預設的runner,如果你沒有指定,那麼系統會自動使用預設runner來運作你的代碼。換句話說,下面兩段代碼含義是完全壹樣的:

import org.junit.runner.runwith;

import org.junit.runners.junit4;

@runwith(junit4.class)

...//省略此處代碼

//用了系統預設的junit4.class,運作效果完全壹樣

 從上述例子可以看出:

  1、要想指定壹個 runner ,需要使用 @runwith 标注,并且把你所指定的 runner 類名作為參數傳遞給它,在junit4.8.2的版本中,系統提供了若幹可以直接使用的runner類型,它們的定義都在包org.junit.runners下面。

  2、注解 @runwith 是用來修飾類的,而不是用來修飾函數的。隻要對壹個類指定了 runner ,那麼這個類中的所有函數都被這個 runner 來調用。

  3、在使用注解@runwith時,要在頭部包含相應的包名,上面的例子對這壹點寫的很清楚了。

  接下來,我會向你們展示其他 runner 的特有功能。

  使用參數化測試完成需要錄入大量資料的測試

  你可能遇到過這樣的函數,它的參數有許多特殊值,或者說他的參數分為很多個區域。比如,壹個對考試分數進行評價的函數,傳回值分别為“優秀,良好,壹般,及格,不及格”,是以你在編寫測試的時候,至少要寫5個測試,把這五種情況都包含了,這确實是壹件很麻煩的事情。這裡我們仍然使用先前的例子,測試壹下square()這個函數,暫且分三類:正數、0、負數。測試代碼如下:

public class advancedtest {

public void testsquare1(){

calculator.square(2);

assertequals(4, calculator.getreuslt());

public void testsquare2(){

calculator.square(0);

assertequals(0, calculator.getreuslt());

public void testsquare3(){

calculator.square(-3);

  為了簡化類似的測試,junit4提出了“參數化測試”的概念,隻寫壹個測試函數,把這若幹種情況的輸入參數和預期的運作結果放在集合中,然後将這個集合作為參數傳遞進去,壹次性的完成測試。代碼如下:

import java.util.arrays;

import java.util.collection;

import org.junit.runners.parameterized;

import org.junit.runners.parameterized.parameters;

@runwith(parameterized.class)

public class squaretest {

@parameters

public static collection preparedata(){

return arrays.aslist(new object[][]{{2,4},{0, 0},{-3, 9}});

public squaretest(int param, int result){

this.param = param;

this.result = result;

public void square(){

calculator.square(param);

assertequals(result, calculator.getreuslt());

private int param;

private int result;

使用 JUnit 進行 Java 代碼的單元測試

最新内容請見作者的github頁:http://qaseven.github.io/