Juint

JUnit4概述

鄭重聲明,這篇文章是轉自博客園的:http://www.cnblogs.com/eggbucket/archive/2012/02/02/2335697.html

寫的非常好的一個入門級別的文章,我怕自己找不到這個網址來看了,所用就轉載了,在此,十分感謝原作者的無私奉獻。


JUnit4是JUnit框架有史以來的最大改進,其主要目標便是利用Java5的Annotation特性簡化測試用例的編寫。

先簡單解釋一下什麼是Annotation,這個單詞一般是翻譯成元數據。元數據是什麼?元數據就是描述數據的數據。也就是說,這個東西在Java裏面可以用來和public、static等關鍵字一樣來修飾類名、方法名、變量名。修飾的作用描述這個數據是做什麼用的,差不多和public描述這個數據是公有的一樣。想具體瞭解可以看Core    Java2。廢話不多說了,直接進入正題。

我們先看一下在JUnit 3中我們是怎樣寫一個單元測試的。比如下面一個類:
public class AddOperation {
     public int add(int x,int y){
         return x+y;
     }
}

我們要測試add這個方法,我們寫單元測試得這麼寫:
import junit.framework.TestCase;
import static org.junit.Assert.*;
public class AddOperationTest extends TestCase{

     public void setUp() throws Exception {
     }

     public void tearDown() throws Exception {
     }

     public void testAdd() {
         System.out.println(\"add\");
         int x = 0;
         int y = 0;
         AddOperation instance = new AddOperation();
         int expResult = 0;
         int result = instance.add(x, y);
         assertEquals(expResult, result);
     }
}

可以看到上面的類使用了JDK5中的靜態導入,這個相對來說就很簡單,只要在import關鍵字後面加上static關鍵字,就可以把後面的類的static的變量和方法導入到這個類中,調用的時候和調用自己的方法沒有任何區別。


我們可以看到上面那個單元測試有一些比較霸道的地方,表現在:
1.單元測試類必須繼承自TestCase。
2.要測試的方法必須以test開頭。

如果上面那個單元測試在JUnit 4中寫就不會這麼複雜。代碼如下:
import junit.framework.TestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;

/**
*
* @author bean
*/
public class AddOperationTest extends TestCase{

     public AddOperationTest() {
     }

     @Before
     public void setUp() throws Exception {
     }

     @After
     public void tearDown() throws Exception {
     }

     @Test
     public void add() {
         System.out.println(\"add\");
         int x = 0;
         int y = 0;
         AddOperation instance = new AddOperation();
         int expResult = 0;
         int result = instance.add(x, y);
         assertEquals(expResult, result);
     }

}
我們可以看到,採用Annotation的JUnit已經不會霸道的要求你必須繼承自TestCase了,而且測試方法也不必以test開頭了,只要以@Test元數據來描述即可。
從上面的例子可以看到在JUnit 4中還引入了一些其他的元數據,下面一一介紹:
@Before:
使用了該元數據的方法在每個測試方法執行之前都要執行一次。

@After:
使用了該元數據的方法在每個測試方法執行之後要執行一次。

注意:@Before和@After標示的方法只能各有一個。這個相當於取代了JUnit以前版本中的setUp和tearDown方法,當然你還可以繼續叫這個名字,不過JUnit不會霸道的要求你這麼做了。

@Test(expected=*.class)
在JUnit4.0之前,對錯誤的測試,我們只能通過fail來產生一個錯誤,並在try塊裏面assertTrue(true)來測試。現在,通過@Test元數據中的expected屬性。expected屬性的值是一個異常的類型

@Test(timeout=xxx):
該元數據傳入了一個時間(毫秒)給測試方法,
如果測試方法在制定的時間之內沒有運行完,則測試也失敗。

@ignore:
該元數據標記的測試方法在測試中會被忽略。當測試的方法還沒有實現,或者測試的方法已經過時,或者在某種條件下才能測試該方法(比如需要一個數據庫聯接,而在本地測試的時候,數據庫並沒有連接),那麼使用該標籤來標示這個方法。同時,你可以爲該標籤傳遞一個String的參數,來表明爲什麼會忽略這個測試方法。比如:@lgnore(“該方法還沒有實現”),在執行的時候,僅會報告該方法沒有實現,而不會運行測試方法。




在Eclipse中使用JUnit4測試(初


我們在編寫大型程序的時候,需要寫成千上萬個方法或函數,這些函數的功能可能很強大,但我們在程序中只用到該函數的一小部分功能,並且經過調試可以確定,這一小部分功能是正確的。但是,我們同時應該確保每一個函數都完全正確,因爲如果我們今後如果對程序進行擴展,用到了某個函數的其他功能,而這個功能有bug的話,那絕對是一件非常鬱悶的事情。所以說,每編寫完一個函數之後,都應該對這個函數的方方面面進行測試,這樣的測試我們稱之爲單元測試。傳統的編程方式,進行單元測試是一件很麻煩的事情,你要重新寫另外一個程序,在該程序中調用你需要測試的方法,並且仔細觀察運行結果,看看是否有錯。正因爲如此麻煩,所以程序員們編寫單元測試的熱情不是很高。於是有一個牛人推出了單元測試包,大大簡化了進行單元測試所要做的工作,這就是JUnit4。本文簡要介紹一下在Eclipse3.2中使用JUnit4進行單元測試的方法。


首先,我們來一個傻瓜式速成教程,不要問爲什麼,Follow Me,先來體驗一下單元測試的快感!


首先新建一個項目叫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”。如下圖所示:




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



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




之後系統會自動生成一個新類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個測試被忽略,一個測試失敗”


           至此,我們已經完整體驗了在Eclipse中使用JUnit的方法。在接下來的文章中,我會詳細解釋測試代碼中的每一個細節!


在Eclipse中使用JUnit4測試(中

我們繼續對初級篇中的例子進行分析。初級篇中我們使用Eclipse自動生成了一個測試框架,在這篇文章中,我們來仔細分析一下這個測試框架中的每一個細節,知其然更要知其所以然,才能更加熟練地應用JUnit4。


一、包含必要地Package


在測試類中用到了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來標註。由於本例比較簡單,沒有用到此功能。


在Eclipse中使用JUnit4測試(高


一、高級Fixture


上一篇文章中我們介紹了兩個Fixture標註,分別是@Before和@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.TestCla***unner;

import org.junit.runner.RunWith;


//使用了系統默認的TestCla***unner,與下面代碼完全一樣

public class CalculatorTest ...{...}


@RunWith(TestCla***unner.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,來表明這個類是一個打包測試類。我們把需要打包的類作爲參數傳遞給該標註就可以了。有了這兩個標註之後,就已經完整的表達了所有的含義,因此下面的類已經無關緊要,隨便起一個類名,內容全部爲空既可。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章