經過前2篇文章,您必定對JUnit有了一個基本的瞭解,下面咱們來探討一下JUnit4中一些高級特性。java
1、高級Fixture數組
上一篇文章中咱們介紹了兩個Fixture標註,分別是@Before和@After,咱們來看看他們是否適合完成以下 功能:有一個類是負責對大文件(超過500兆)進行讀寫,他的每個方法都是對文件進行操做。換句話說,在調用每個方法以前,咱們都要打開一個大文件並 讀入文件內容,這絕對是一個很是耗費時間的操做。若是咱們使用@Before和@After,那麼每次測試都要讀取一次文件,效率及其低下。這裏咱們所希 望的是在全部測試一開始讀一次文件,全部測試結束以後釋放文件,而不是每次測試都讀文件。JUnit的做者顯然也考慮到了這個問題,它給出了 @BeforeClass 和 @AfterClass兩個Fixture來幫咱們實現這個功能。從名字上就能夠看出,用這兩個Fixture標註的函數,只在測試用例初始化時執行 @BeforeClass方法,當全部測試執行完畢以後,執行@AfterClass進行收尾工做。在這裏要注意一下,每一個測試類只能有一個方法被標註爲 @BeforeClass 或 @AfterClass,而且該方法必須是Public和Static的。框架
二、限時測試ide
還記得我在初級篇中給出的例子嗎,那個求平方根的函數有Bug,是個死循環:函數
public void squareRoot(int n) { for(;;); //Bug:死循環 }
若是測試的時候遇到死循環,你的臉上絕對不會露出笑容。所以,對於那些邏輯很複雜,循環嵌套比較深的程序,頗有可能出現 死循環,所以必定要採起一些預防措施。限時測試是一個很好的解決方案。咱們給這些測試函數設定一個執行時間,超過了這個時間,他們就會被系統強行終止,並 且系統還會向你彙報該函數結束的緣由是由於超時,這樣你就能夠發現這些Bug了。要實現這一功能,只須要給@Test標註加一個參數便可,代碼以下:測試
@Test(timeout=1000) public void testAdd() { calculator.squareRoot(4); assertEquals(2, calculator.getResult()); }
Timeout參數代表了你要設定的時間,單位爲毫秒,所以1000就表明1秒。ui
3、測試異常this
JAVA中的異常處理也是一個重點,所以你常常會編寫一些須要拋出異常的函數。那麼,若是你以爲一個函數應該拋出異常, 可是它沒拋出,這算不算Bug呢?這固然是Bug,並JUnit也考慮到了這一點,來幫助咱們找到這種Bug。例如,咱們寫的計算器類有除法功能,若是除 數是一個0,那麼必然要拋出「除0異常」。所以,咱們頗有必要對這些進行測試。代碼以下:spa
@Test(expected =ArithmeticException.class) public void testDivide() { calculator.add(8); calculator.divide(0); assertEquals(4,calculator.getResult()); }
如上述代碼所示,咱們須要使用
@Test標註的expected屬性,將咱們要檢驗的異常傳遞給他,這樣JUnit框架就能自動幫咱們檢測是否拋出了咱們指定的異常。3d
4、Runner (運行器)
你們有沒有想過這個問題,當你把測試代碼提交給JUnit框架後,框架如何來運行你的代碼呢?答案就是——Runner。在JUnit中有不少個Runner,他們負責調用你的測試代碼,每個Runner都有各自的特殊功能,你要根據須要選擇不一樣的Runner來運行你的測試代碼。可能你會以爲奇怪,前面咱們寫了那麼多測試,並無明確指定一個Runner啊?這是由於JUnit中有一個默認Runner,若是你沒有指定,那麼系統自動使用默認Runner來運行你的代碼。換句話說,下面兩段代碼含義是徹底同樣的:
import org.junit.internal.runners.TestClassRunner; import org.junit.runner.RunWith; @RunWith(TestClassRunner.class) public class CalculatorTest { ... } //使用了系統默認的TestClassRunner,與下面代碼徹底同樣 public class CalculatorTest { ... }
從上述例子能夠看出,要想指定一個Runner,須要使用@RunWith標註,而且把你所指定的Runner做爲參數傳遞給它。另一個要注意的是,@RunWith是用來修飾類的,而不是用來修飾函數的。只要對一個類指定了Runner,那麼這個類中的全部函數都被這個Runner來調用。最後,不要忘了包含相應的Package哦,上面的例子對這一點寫的很清楚了。接下來,我會向大家展現其餘Runner的特有功能。
5、參數化測試
你可能遇到過這樣的函數,它的參數有許多特殊值,或者說他的參數分爲不少個區域。好比,一個對考試分數進行評價的函數, 返回值分別爲「優秀,良好,通常,及格,不及格」,所以你在編寫測試的時候,至少要寫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.*; import java.util.Collection; import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @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()); System.out.println("result="+result); } }
下面咱們對上述代碼進行分析。首先,你要爲這種測試專門生成一個新的類,而不能與其餘測試共用同一個類,此例中咱們定義了一個SquareTest類。而後,你要爲這個類指定一個Runner,而不能使用默認的Runner了,由於特殊的功能要用特殊的Runner嘛。
@RunWith(Parameterized.class)這條語句就是爲這個類指定了一個ParameterizedRunner。
第二步,定義一個待測試的類,而且定義兩個變量,一個用於存放參數,一個用於存放期待的結果。
接下來,定義測試數據的集合,也就是上述的data()方法,該方法能夠任意命名,但是必須使用@Parameters標註進行修飾。這個方法的框架就不予解釋了,你們只須要注意其中的數據,是一個二維數組,數據兩兩一組,每組中的這兩個數據,一個是參數,一個是你預期的結果。好比咱們的第一組{2, 4},2就是參數,4就是預期的結果。這兩個數據的順序無所謂,誰前誰後均可以。
以後是構造函數,其功能就是對先前定義的兩個參數進行初始化。在這裏你可要注意一下參數的順序了,要和上面的數據集合的 順序保持一致。若是前面的順序是{參數,期待的結果},那麼你構造函數的順序也要是「構造函數(參數, 期待的結果)」,反之亦然。最後就是寫一個簡單的測試例了,和前面介紹過的寫法徹底同樣,在此就很少說。
6、打包測試
經過前面的介紹咱們能夠感受到,在一個項目中,只寫一個測試類是不可能的,咱們會寫出不少不少個測試類。但是這些測試類 必須一個一個的執行,也是比較麻煩的事情。鑑於此,JUnit爲咱們提供了打包測試的功能,將全部須要運行的測試類集中起來,一次性的運行完畢,大大的方 便了咱們的測試工做。具體代碼以下:
import org.junit.Assert.*; 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,來代表這個類是一個打包測試類。咱們把須要打包的類做爲 參數傳遞給該標註就能夠了。有了這兩個標註以後,就已經完整的表達了全部的含義,所以下面的類已經可有可無,隨便起一個類名,內容所有爲空既可。
JUnit提供了一種批量運行測試類的方法,叫測試套件。
測試套件的寫法須要遵循如下原則:
1. 建立一個空類做爲測試套件的入口;
2. 使用註解 org.junit.runner.RunWith 和 org.junit.runners.Suite.SuitClasses 修飾這個空類
3. 將org.junit.runners.Suite做爲參數傳入給註解RunWith,以提示Junit爲此類測試使用套件運行器執行。
4. 將須要放入此測試套件的測試類組成數組做爲註解SuiteClasses的參數
5. 保證這個空類使用public修飾,並且存在公開的不帶任何參數的構造函數.
至此,本系列文章所有結束,但願可以對你們使用JUnit4有所幫助。