1. 程式人生 > >JUnit4單元測試入門教程

JUnit4單元測試入門教程

本文按以下順序講解JUnit4的使用

  • 下載jar包
  • 單元測試初體驗
  • 自動生成測試類
  • 執行順序
  • @Test的屬性

下載jar包##

下載地址 在github上,把以下兩個jar包都下載下來。

   

下載junit-4.12.jar,junit-4.12-javadoc.jar(文件),junit-4.12-sources.jar(原始碼)。


 
 

下載hamcrest-core-1.3.jar,hamcrest-core-1.3-javadoc.jar(文件),hamcrest-core-1.3-sources.jar(原始碼)。


   

最前面那個pom是Maven的配置檔案,如果你需要的話也下載下來。

單元測試初體驗##

先建立個簡單的專案體驗下單元測試是怎麼一回事吧。

我建立一個專案叫JUnit4Demo,然後建立一個lib資料夾放剛下載的<code>junit-4.12.jar</code>和<code>hamcrest-core-1.3.jar</code>兩個jar包並匯入到專案裡。
建立一個類com.xuhongchuan.util.Math,然後輸入一個求階乘的方法:

package com.xuhongchuan.util;

/**
 * Created by xuhongchuan on 2015/7/18.
 */
public class Math { /** * 階乘 * @param n * @return */ public int factorial(int n) throws Exception { if (n < 0) { throw new Exception("負數沒有階乘"); } else if (n <= 1) { return 1; } else { return n * factorial(n - 1); } } } 

此時的專案結構是這樣的:


   

好了,接下來要建立一個類來對Math類進行單元測試。
建立一個和src同級別的資料夾叫test(邏輯程式碼放src裡,測試程式碼放test裡是個好習慣)。
接著在IntelliJ IDEA裡還要把這個test資料夾要設定成測試檔案的根目錄,右鍵選中
Mark Directory As - Test Sources Root。

   

然後建立com.xuhongchuan.util.MathTest類(包名一致,類名在要測試的類名後加上Test也是個好習慣)。
在MathTest裡輸入以下內容:

package com.xuhongchuan.util;

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

/** * Created by xuhongchuan on 2015/7/18. */ public class MathTest { @Test public void testFactorial() throws Exception { assertEquals(120, new Math().factorial(5)); } } 

然後選中MathTest類ctrl + shift + F10執行一下,結果如下。


   

右下方一條綠色條說明測試通過,如果把120改成別的數字那麼就會測試不通過顯色紅色條。JUnit4有一句話叫:“keeps the bar green to keep the code clean”。

解釋一下MathTest,就六個地方要講:
第一,匯入了org.junit.Test;和org.junit.Assert.*;這兩個包,注意後者是靜態匯入import static。
第二,testFactorial是在要測試的方法名Factorial前加個test(這也是個好習慣)。
第三,所有測試方法返回型別必須為void且無引數。
第四,一個測試方法之所以是個測試方法是因為@Test這個註解。
第五,assertEquals的作用是判斷兩個引數是否相等,例子中120是預期結果,new Math().factorial(5)是實際結果。但是通常不應該只比較一個值,要測試多幾個特殊值,特別是臨界值。例如Math().factorial(0)和Math().factorial(-1)等。
第六,assertEquals除了比較兩個int,還過載了好多次可以比較很多種型別的引數。而且JUnit4包含一堆assertXX方法,assertEquals只是其中之一,這些assertXX統稱為斷言。剛不是下載了junit-4.12-javadoc.jar這個文件嗎,解壓後開啟index.html如下圖還有一堆斷言。


   

自動生成測試類##

我們把測試類MathTest刪掉,回到邏輯程式碼Math裡再新增一個方法求斐波那契數列:

    /**
     * 斐波那契數列
     * @param n
     * @return
     */
    public int fibonacci(int n) throws Exception { if (n <= 0) { throw new Exception("斐波那契數列從第1位開始"); } else if (n == 1) { return 0; } else if (n == 2) { return 1; } else { return fibonacci(n - 1) + fibonacci(n - 2); } } 

現在的專案結構是這樣的(測試類MathTest被刪掉了)。


   

現在Math類有兩個方法了,這裡假設有十個、二十個甚至更多方法,如果要寫測試方法都要自己一個一個寫嗎?那太累了,IntelliJ IDEA是可以自動生成測試方法的基本結構的。按快捷鍵ctrl - shift - T。
彈出的對話方塊點選Create New Test...

   

選擇JUnit4,類名和包名還是預設的已經符合規範了,然後勾選要生成測試方法的方法。點選OK。


   

點選自動生成的測試類MathTest,可以看到測試方法的基本結構已經自動生成了。我們再自己新增測試程式碼就行了。
在testFactorial()新增:

assertEquals(120, new Math().factorial(5));

在testFibonacci()方法新增:

assertEquals(21, new Math().fibonacci(9));

執行後,綠條又出現了,測試成功。


   

執行順序##

JUnit4利用JDK5的新特性Annotation,使用註解來定義測試規則。
這裡講一下以下幾個常用的註解:

  • @Test:把一個方法標記為測試方法
  • @Before:每一個測試方法執行前自動呼叫一次
  • @After:每一個測試方法執行完自動呼叫一次
  • @BeforeClass:所有測試方法執行前執行一次,在測試類還沒有例項化就已經被載入,所以用static修飾
  • @AfterClass:所有測試方法執行完執行一次,在測試類還沒有例項化就已經被載入,所以用static修飾
  • @Ignore:暫不執行該測試方法

我們來試驗一下,我新建一個測試類AnnotationTest,然後每個註解都用了,其中有兩個用@Test標記的方法分別是test1和test2,還有一個用@Ignore標記的方法test3。然後我還建立了一個構造方法,這個構造方法很重要一會會引出一個問題。
具體程式碼如下:

package com.xuhongchuan.util;

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

/** * Created by xuhongchuan on 2015/7/18. */ public class AnnotationTest { public AnnotationTest() { System.out.println("構造方法"); } @BeforeClass public static void setUpBeforeClass() { System.out.println("BeforeClass"); } @AfterClass public static void tearDownAfterClass() { System.out.println("AfterClass"); } @Before public void setUp() { System.out.println("Before"); } @After public void tearDown() { System.out.println("After"); } @Test public void test1() { System.out.println("test1"); } @Test public void test2() { System.out.println("test2"); } @Ignore public void test3() { System.out.println("test3"); } } 

執行結果如下:
<pre>
BeforeClass
構造方法
Before
test1
After
構造方法
Before
test2
After
AfterClass
</pre>

解釋一下:@BeforeClass和@AfterClass在類被例項化前(構造方法執行前)就被呼叫了,而且只執行一次,通常用來初始化和關閉資源。@Before和@After和在每個@Test執行前後都會被執行一次。@Test標記一個方法為測試方法沒什麼好說的,被@Ignore標記的測試方法不會被執行,例如這個模組還沒完成或者現在想測試別的不想測試這一塊。
以上有一個問題,構造方法居然被執行了兩次。所以我這裡要說明一下,JUnit4為了保證每個測試方法都是單元測試,是獨立的互不影響。所以每個測試方法執行前都會重新例項化測試類。

我再給你看一個實驗:
新增一個成員變數

int i = 0;

然後把test1改為:

    i++;
    System.out.println("test1的i為" + i); 

test2改為:

    i++;
    System.out.println("test2的i為" + i); 

執行結果:
<pre>
BeforeClass
構造方法
Before
test1的i為1
After
構造方法
Before
test2的i為1
After
AfterClass
</pre>

可以看到test1和test2的i都只自增了一次,所以test1的執行不會影響test2,因為執行test2時又把測試類重新例項化了一遍。如果你希望test2的執行受test1的影響怎麼辦呢?把int i改為static的唄。

最後關於這些註解還有一個要說明的就是,你可以把多個方法標記為@BeforeClass、@AfterClass、@Before、@After。他們都會在相應階段被執行。

@Test的屬性##

最後來說一下@Test的兩個屬性

  • excepted
  • timeout
    excepted屬性是用來測試異常的,我們回到Math類,拿其中的求階乘方法factorial()來說。
    public int factorial(int n) throws Exception { if (n < 0) { throw new Exception("負數沒有階乘"); } else if (n <= 1) { return 1; } else { return n * factorial(n - 1); } } 

如果傳進來一個負數我們是希望丟擲異常的,那要測試會不會拋異常怎麼辦呢?
我在測試類MathTest新增一個測試方法:

    @Test(expected = Exception.class)
    public void testFactorialException() throws Exception { new Math().factorial(-1); fail("factorial引數為負數沒有丟擲異常"); } 

這個方法就是(expected = Exception.class)和fail("factorial引數為負數沒有丟擲異常");之間的配合。就是這個測試方法會檢查是否丟擲Exception異常(當然也可以檢測是否丟擲其它異常),如果丟擲了異常那麼測試通過(因為你的預期就是傳進負數會丟擲異常)。沒有丟擲異常則測試不通過執行fail("factorial引數為負數沒有丟擲異常");

然後說下timeout屬性,這個是用來測試效能的,就是測試一個方法能不能在規定時間內完成。
回到Math類,我建立一個數組排序的方法,用的是氣泡排序。

    public void sort(int[] arr) { //氣泡排序 for (int i = 0; i < arr.length - 1; i++) { //控制比較輪數 for (int j = 0; j < arr.length - i - 1; j++) { //控制每輪的兩兩比較次數 if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } 

然後偶在測試類MathTest建立測試方法,隨機生成一個長度為50000的陣列然後測試排序所用時間。timeout的值為2000,單位和毫秒,也就是說超出2秒將視為測試不通過。

    @Test(timeout = 2000)
    public void testSort() throws Exception { int[] arr = new int[50000]; //陣列長度為50000 int arrLength = arr.length; //隨機生成陣列元素 Random r = new Random(); for (int i = 0; i < arrLength; i++) { arr[i] = r.nextInt(arrLength); } new Math().sort(arr); } 

執行結果測試不通過,且提示TestTimedOutException。


   

那怎麼辦,修改程式碼提升效能唄。回到Math方法改為下sort()。這次我用快速排序,程式碼如下:

    public void sort(int[] arr) { //快速排序 if (arr.length <= 1) { return; } else { partition(arr, 0, arr.length - 1); } } static void partition(int[] arr, int left, int right) { int i = left; int j = right; int pivotKey = arr[left]; //基準數 while (i < j) { while (i < j && arr[j] >= pivotKey) { j--; } while (i < j && arr[i] <= pivotKey) { i++; } if (i < j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } if (i != left) { arr[left] = arr[i]; arr[i] = pivotKey; } if (i - left > 1) { partition(arr, left, i - 1); } if (right - j > 1) { partition(arr, j + 1, right); } } 

然後再執行一下測試類MathTest,綠色條出現了,測試通過妥妥的。


   

本文程式碼下載:百度網盤



作者:許巨集川
連結:https://www.jianshu.com/p/7088822e21a3
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。