單元測試與Junit4測試框架 2018-05-15

1 單元測試與Junit4測試框架:

單元測試:是指對軟件中的最小可測試單元進行檢查和驗證。在java應用程序中常常指的是一個方法(但并不總是如此)。

Junit4測試框架:框架是一個應用程序的半成品。框架提供了可在應用程序之間共享的課服用的公共結構。例如下面在Eclipse中運行Junit4生成的框架:

package com.huawei.osm.incident.utils;

import static org.junit.Assert.*;

import org.junit.After;

import org.junit.AfterClass;

import org.junit.Before;

import org.junit.BeforeClass;

import org.junit.Test;

public class MybatisUtilsTest

{? ?

? ? @BeforeClass

? ? public static void setUpBeforeClass() throws Exception

? ? {

? ? }? ?

? ? @AfterClass

? ? public static void tearDownAfterClass() throws Exception

? ? {

? ? }? ?

? ? @Before

? ? public void setUp() throws Exception

? ? {

? ? }? ?

? ? @After

? ? public void tearDown() throws Exception

? ? {

? ? }? ?

? ? @Test

? ? public void testGetSqlSession()

? ? {

? ? ? ? fail("Not yet implemented");

? ? }? ?

? ? @Test

? ? public void testCloseSqlSession()

? ? {

? ? ? ? fail("Not yet implemented");

? ? }? ?

}

2 了解Junit4新特性:

2.1 Junit4新特性

1. 必須引入的Test類:import org.junit.Test。舊的Junit3是需要繼承Test。以及新的類:org.junit.Assert.*。

2. 采用@Befor和@After加載或者清除資源。相當于許多方法類中的init()和destroy();

3. 屬于類范圍的 setUp()方法 和 tearDown() 方法,任何用 @BeforeClass 注釋的方法都將在該類中的測試方法運行之前運行一次,而任何用 @AfterClass 注釋的方法都將在該類中的所有測試都運行之后運行一次;

4. 提供@Test(expected=“可能拋出的異常”)對異常測試的注解

5. 提供@lgnore注解,意即被@Ignore標記的方法會在運行測試時跳過,或者說被改元素標記的方法在測試中會被忽略。當測試的方法還沒有實現,或者測試的方法已經過時,或者在某種條件下才能測試該方法(比如需要一個數據庫聯接,而在本地測試的時候,數據庫并沒有連接),那么使用該標簽來標示這個方法。同時,你可以為該標簽傳遞一個String的參數,來表明為什么會忽略這個測試方法。比如:@lgnore(“該方法還沒有實現”),在執行的時候,僅會報告該方法沒有實現,而不會運行測試方法。

6. 提供測試響應時間@Test(timeout=1000)(單位:毫秒),如果測試的運行時間超過指定的毫秒數,即認為測試失敗。

7. 增加兩個斷言方法:

(1)public static void assertEquals(Object[] expected, Object[] actual)

(2)public static void assertEquals(String message, Object[] expected, Object[] actual) 這兩用來比較數組:如果數組的長度和對應的元素相同,測這兩個數組相等,否則不等,也考慮了數組為空的情況。

2.2 Junit4中常見的幾個annotation釋義

@Test:測試方法,可以測試期望異常和超市時間

@Before:初始化方法,每次執行@Test之前都會運行一次。

@After:資源釋放,每次執行@Test之后都會運行一次。

@Ignore:忽略的測試方法

@BeforeClass、@AfterClass:注意區分@Before,它是針對所有的測試,在所在類中只會執行一次,只能定義為static void。同理可以接@AfterClass。

2.3 Junit4的單元測試用例執行順序為

@BeforeClass>>@Before>>@Test>>@After>>@AfterClass.

3 Junit4測試案例

3.1 Eclipse中使用Junit4

下面是我們在Eclipse中新建的一個用來做測試的一個計算器類Claculator

package com.huawei.demo;

public class Calculator

{

? ? private static int result;

? ? public void add(int n)

? ? {

? ? ? ? result = result + n;? ? ? ?

? ? }

? ? public void subs(int n)

? ? {?

? ? ? ? result = result - n;? ? ? ?

? ? }

? ? // 此方法尚未寫好

public void multiply(int n)

{? ?

// 此方法尚未寫好? ?

? ? }? ?

? ? public void divide(int n)

? ? {

? ? ? ? result = result / n;

? ? }

? ? public void square(int n)

? ? {

? ? ? ? result = n * n;

? ? }

? ? //Bug : 死循環

? ? public void squareRoot(int n)

? ? {

? ? ? ? for (; ;) ;?

? ? }

? ? // 將結果清零

? ? public void clear()

? ? {

? ? ? ? result = 0;

? ? }

? ? public int getResult()

? ? {

? ? ? ? return result;

}

}

第二步,在我們的項目中引入JUnit4的jar包(略)

第三步,右鍵點擊我們剛才新建的Claculator類,選擇“New”新建一個“JUnit Test Case”,如圖:

點擊“下一步”,進入勾選測試方法的頁面,根據情況選擇需要做測試的單元,如圖:

點擊“完成”。Eclipse會自動幫我們生成Junit4的測試框架,如下:

package com.huawei.demoTest;

import static org.junit.Assert.*;

import org.junit.Before;

import org.junit.Test;

public class CalculatorTest

{? ?

? ? @Before

? ? public void setUp() throws Exception

? ? {

? ? }? ?

? ? @Test

? ? public void testAdd()

? ? {

? ? ? ? fail("Not yet implemented");

? ? }? ?

? ? @Test

? ? public void testSubs()

? ? {

? ? ? ? fail("Not yet implemented");

? ? }? ?

? ? @Test

? ? public void testMultiply()

? ? {

? ? ? ? fail("Not yet implemented");

? ? }? ?

? ? @Test

? ? public void testDivide()

? ? {

? ? ? ? fail("Not yet implemented");

? ? }? ?

? ? @Test

? ? public void testSquare()

? ? {

? ? ? ? fail("Not yet implemented");

? ? }? ?

? ? @Test

? ? public void testSquareRoot()

? ? {

? ? ? ? fail("Not yet implemented");

? ? }

}

第四步:編寫我們的測試內容。(在Calculator類中預留的bug和exception都得處理好。)修改后如下:

package com.huawei.demoTest;

import static org.junit.Assert.*;

import org.junit.Before;

import org.junit.Ignore;

import org.junit.Test;

import com.huawei.demo.Calculator;

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);

calculator.add(-5);

? ? ? ? assertEquals(0, calculator.getResult());

? ? }? ?

? ? @Test

? ? public void testSubs()

? ? {

? ? ? ? calculator.add(10);

? ? ? ? calculator.subs(2);

? ? ? ? assertEquals(8, calculator.getResult());

? ? }

? ? @Ignore("Multiply() Not yet implemented")

? ? @Test

? ? public void testMultiply()

? ? {

? ? }? ?

? ? @Test

? ? public void testDivide()

? ? {

? ? ? ? calculator.add(16);

? ? ? ? calculator.divide(-2);

calculator.divide(-2);

? ? ? ? assertEquals(4, calculator.getResult());

? ? }

? ? @Test(expected = ArithmeticException.class)

? ? public void divideByZero()

? ? {

? ? ? ? calculator.divide(0);

? ? }

? ? @Test

? ? public void testSquare()

{

calculator.square(-2);

? ? ? ? calculator.square(4);

? ? ? ? assertEquals(16, calculator.getResult());

? ? }? ?

? ? @Test(timeout = 1000)

? ? public void testSquareRoot()

? ? {

? ? ? ? calculator.squareRoot(4);

? ? ? ? assertEquals(2, calculator.getResult());

}

}

如下:運行改單元測試案例,可以查看測試結果:

3.2 Gradle中集成Junit4單元測試

第一步:按照gradle項目目錄結構新建一個“gradle-junit”的項目工程,該工程下包含標示gradle項目的build.gradle文件。并將我們剛才編寫的Calculator類和它的單元測試案例類CalculatorTest放在對應的目錄中。

第二步:編寫我們的build.gradle文件,如下

第三步:執行gradle命令。命令行模式下,進入我們剛才仙劍的“gradle-junit”工程所在的目錄,執行gradle test命令。等待gradle構建完畢,從窗口可以看到本次執行的結果,如下:

結果分析解讀:

1. 執行testSquareRoot(死循環)測試失敗,并報TestTimedOutException異常;

2. 總共測試了7個tests,1個失敗了(failed),1個跳過了(skipped);

3. 最后最大的亮點在,gradle為我們生成了本次測試的html頁面報告文件(…/build/reports/tests/test/index.html),可以非常直觀的顯示本次構建結果,并且還支持鏈接查看類和我們剛才編寫的單元測試案例。

4. 點擊紅色標記的包或者類可以進入相應的鏈接頁面,查看更詳細的結果。例如點擊下面的CalculatorTest鏈接可以查看測試失敗單元測試案例原因:

TestTimedOutException:test timed out after 1000 milliseconds

以及測試類的一個測試結果類的小結,如下圖:

(可以看見執行testSquareRoot測試任務花費1.010s,超過我們設定的閥值1s;

并且testMultiply測試任務變色。)

4 單元測試代碼覆蓋率

在做單元測試時,代碼覆蓋率常常被拿來作為衡量測試好壞的指標,甚至,有用代碼覆蓋率來考核測試任務完成情況,比如代碼覆蓋率必須達到80%或90%。正確理解代碼覆蓋率,對我們設計案例覆蓋代碼很有必要,因為僅僅使用代碼覆蓋率來衡量,有利也有弊。

4.1 語句覆蓋

語句覆蓋就是度量被測試代碼中每個可執行語句是否被執行到了,簡單的說就是只統計能夠執行的代碼被執行了多少行。語句覆蓋常常被人指責為“最弱的覆蓋”,它只管覆蓋代碼中執行語句,卻不考慮各種分支的組合等等。例如:

測試代碼如下:

int foo (int a,int b)

{

return a/b;

}

假如我們設計如下的測試案例:

TeseCase:a = 10,b = 5

雖然代碼覆蓋率到達了100%,并且所有的測試案例都通過了。然而遺憾的是,我們的語句覆蓋率到達了所謂的100%,但是卻沒有發現最賤的bug,比如當我們取b=0時,會拋出異常。再如下面的例子:

int foo (int a, int b)

{

int result = 0

if (a < 10) {

result += 1;

}

if (b < 10) {

result += 10;

}

return resule;

}

設計的測試案例如下:

TeseCase:a = 5, b = 5

該案例保證了測試案例的語句都執行了,包括分支中執行的語句,因此語句覆蓋率也達到了100%,但是從分支邏輯上明顯覆蓋不全。

4.2 判定覆蓋和條件覆蓋

判定覆蓋又稱分支覆蓋,它是度量程序中沒一個判定的分支是否都被測試到了。這句話需要進一步理解,很容易和條件覆蓋混淆。我們看例子:

int foo (int a, int b)

{

if (a < 10 || b < 10)

{

return 0; //分支一

}else {

return 1;//分支二

}

}

設計判定覆蓋案例時,我們只需考慮判定結果為true和false兩種情況,我們設計如下的案例就能達到判定覆蓋率為100%:

TeseCase1:a = 5, b = 任意數 覆蓋了分支一

TeseCase2:a = 15, b = 15 覆蓋了分支二

從設計條件的邏輯上出發,TeseCase2的設計與TeseCase1的在邏輯上完全的互斥互逆的,簡單理解為除了分支一的情況,就只剩下分支二了。

設計條件覆蓋案例時,我們需要考慮判定中的每個條件表達結果,為了覆蓋率達到100%,我們設計如下的案例:

TeseCase1:a = 5, b = 5 true, true

TeseCase2:a = 15,b = 15 false, false

由此可見,條件覆蓋不是講判定中的每一個田間表達式的結果進行排列組合,而只要每個條件表達式的結果為true和false測試到了就OK了。說白了,條件覆蓋就是不考慮邏輯分支僅覆蓋條件分支,執行條件分支語句的覆蓋。因此,我們得出這樣的推論:完全的條件覆蓋并不能覆蓋保證完全的判定覆蓋。

4.3 路徑覆蓋

對于路徑覆蓋的理解,我們設計如下的案例:

int foo (int a, int b)

{

int result = 0

if (a < 10) {

result += 1;

}

if (b < 10) {

result += 10;

}

return resule;

}

測試案例:

TsseCase1:a = 5, b = 5

TeseCase2:a = 5, b = 15

TeseCase3:a = 15, b = 5

TeseCase4:a = 15, b = 15

路徑覆蓋又稱斷言覆蓋。它度量了是否函數的每一個分支都被執行了。這句話也非常好理解,就是所有的可能分支都執行一遍,有多個分支嵌套時,需要對多個分支進行排列組合,可想而知,測試的路徑隨著分支的數量呈2?指數級別的增加。

5 Jacoco單元測試覆蓋率

Jacoco專門用于統計單元測試覆蓋率的,在gradle中和‘java’插件一樣使用時直接引用,并添加版本即可:

apply plugin: 'jacoco'

jacoco{? toolVersion = "0.7.1.201405082137"? }

運行jacoco也非常簡單,因為他依賴于單元測試的完成,所以執行完test任務后,可以緊接著執行gradle jacoco命令,執行成功,也會生成一個類似單元測試的html結果報告,非常直觀的可以看到統計數據的結果。

5.1 Jacoco案例分析(一):語句覆蓋與判定覆蓋

源碼如下:

package com.huawei.demo;

public class CoverJudge

{

? ? public int foot(int a, int b)

? ? {

? ? ? ? int result = 0;

? ? ? ? if (a < 10)

? ? ? ? {

? ? ? ? ? ? result += 1;

? ? ? ? }

? ? ? ? if (b < 10)

? ? ? ? {

? ? ? ? ? ? result += 10;

? ? ? ? }

? ? ? ? return result;

? ? }? ?

}

下面是設計的單元測試案例:

package com.huawei.demoTest;

import static org.junit.Assert.*;

import org.junit.Test;

import com.huawei.demo.CoverJudge;

public class CoverJudgeTest

{

? ? private static CoverJudge coverJudge = new CoverJudge();

//簡單的語句覆蓋

@Test

? ? public void testFoot()

? ? {

? ? ? ? int a = 5;

? ? ? ? int b = 5;

? ? ? ? assertEquals(11, coverJudge.foot(a, b));

? ? }

? ? /*@Test

? ? public void testFoot()

? ? {

? ? ? ? int a = 5;

? ? ? ? int b = 15;

? ? ? ? assertEquals(1, coverJudge.foot(a, b));

? ? }

@Test

? ? public void testFoot1()

? ? {

? ? ? ? int a = 15;

? ? ? ? int b = 5;

? ? ? ? assertEquals(10, coverJudge.foot(a, b));

? ? }*/? ?

}

運行gradle test jacoco,構建完成后可以查看覆蓋率報告,如下:

顯示分支覆蓋率(Missed Branches)僅為50%。

5.2 Jacoco案例分析(二):多分支覆蓋

源碼如下:

package com.huawei.demo;

public class MathUtil

{

? ? public int max(int a, int b, int c)

? ? {

? ? ? ? if (a > b)

? ? ? ? {

? ? ? ? ? ? if (a > c)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return a;

? ? ? ? ? ? }

? ? ? ? ? ? else

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return c;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? else

? ? ? ? {

? ? ? ? ? ? if (b > c)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return b;

? ? ? ? ? ? }

? ? ? ? ? ? else

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return c;

? ? ? ? ? ? }

? ? ? ? }

? ? }

}

下面是設計的單元測試案例:

TeseCase1:

package com.huawei.demoTest;

import static org.junit.Assert.*;

import org.junit.Test;

import com.huawei.demo.MathUtil;

public class MathUtilTest

{

? ? private static MathUtil mathUtil = new MathUtil();

? ? @Test

? ? public void testMax1_2_3()

? ? {

? ? ? ? assertEquals(3, mathUtil.max(1, 2, 3));

? ? }? ?

}

執行后結果如下,還可以進入鏈接查看詳情

TestCase2:

package com.huawei.demoTest;

import static org.junit.Assert.*;

import org.junit.Test;

import com.huawei.demo.MathUtil;

public class MathUtilTest2

{

? ? private static MathUtil mathUtil = new MathUtil();

? ? @Test

? ? public void testMax1_2_3()

? ? {

? ? ? ? assertEquals(3, mathUtil.max(1, 2, 3));

? ? }

@Test

public void test_max_1_3_2() {

assertEquals(3, mathUtil.max(1, 3, 2));

}

@Test

public void test_max_3_2_1() {

assertEquals(3, mathUtil.max(3, 2, 1));

}

@Test

public void test_max_0_0_0(){

assertEquals(0, mathUtil.max(0, 0, 0));

}

@Test

public void test_max_0_1_0(){

assertEquals(1, mathUtil.max(0, 1, 0));

}? ?

}

執行后結果如下:

點擊鏈接還可以查詢沒有覆蓋到的分支:

紅色表示沒有覆蓋到的分支;分析沒有覆蓋到a > b且a < c的情況,所以可以設計如下Tesecase:a = 2, b = 1, c = 3的情況。

所以增加如下單元測試案例:

@Test

public void test_max_2_1_3() {

assertEquals(3, mathUtil.max(2, 1, 3));

}

再次執行可以顯示100%的分支代碼覆蓋率。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容