單元測(cè)試與Junit4測(cè)試框架 2018-05-15

1 單元測(cè)試與Junit4測(cè)試框架:

單元測(cè)試:是指對(duì)軟件中的最小可測(cè)試單元進(jìn)行檢查和驗(yàn)證。在java應(yīng)用程序中常常指的是一個(gè)方法(但并不總是如此)。

Junit4測(cè)試框架:框架是一個(gè)應(yīng)用程序的半成品??蚣芴峁┝丝稍趹?yīng)用程序之間共享的課服用的公共結(jié)構(gòu)。例如下面在Eclipse中運(yùn)行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加載或者清除資源。相當(dāng)于許多方法類中的init()和destroy();

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

4. 提供@Test(expected=“可能拋出的異?!?對(duì)異常測(cè)試的注解

5. 提供@lgnore注解,意即被@Ignore標(biāo)記的方法會(huì)在運(yùn)行測(cè)試時(shí)跳過(guò),或者說(shuō)被改元素標(biāo)記的方法在測(cè)試中會(huì)被忽略。當(dāng)測(cè)試的方法還沒(méi)有實(shí)現(xiàn),或者測(cè)試的方法已經(jīng)過(guò)時(shí),或者在某種條件下才能測(cè)試該方法(比如需要一個(gè)數(shù)據(jù)庫(kù)聯(lián)接,而在本地測(cè)試的時(shí)候,數(shù)據(jù)庫(kù)并沒(méi)有連接),那么使用該標(biāo)簽來(lái)標(biāo)示這個(gè)方法。同時(shí),你可以為該標(biāo)簽傳遞一個(gè)String的參數(shù),來(lái)表明為什么會(huì)忽略這個(gè)測(cè)試方法。比如:@lgnore(“該方法還沒(méi)有實(shí)現(xiàn)”),在執(zhí)行的時(shí)候,僅會(huì)報(bào)告該方法沒(méi)有實(shí)現(xiàn),而不會(huì)運(yùn)行測(cè)試方法。

6. 提供測(cè)試響應(yīng)時(shí)間@Test(timeout=1000)(單位:毫秒),如果測(cè)試的運(yùn)行時(shí)間超過(guò)指定的毫秒數(shù),即認(rèn)為測(cè)試失敗。

7. 增加兩個(gè)斷言方法:

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

(2)public static void assertEquals(String message, Object[] expected, Object[] actual) 這兩用來(lái)比較數(shù)組:如果數(shù)組的長(zhǎng)度和對(duì)應(yīng)的元素相同,測(cè)這兩個(gè)數(shù)組相等,否則不等,也考慮了數(shù)組為空的情況。

2.2 Junit4中常見(jiàn)的幾個(gè)annotation釋義

@Test:測(cè)試方法,可以測(cè)試期望異常和超市時(shí)間

@Before:初始化方法,每次執(zhí)行@Test之前都會(huì)運(yùn)行一次。

@After:資源釋放,每次執(zhí)行@Test之后都會(huì)運(yùn)行一次。

@Ignore:忽略的測(cè)試方法

@BeforeClass、@AfterClass:注意區(qū)分@Before,它是針對(duì)所有的測(cè)試,在所在類中只會(huì)執(zhí)行一次,只能定義為static void。同理可以接@AfterClass。

2.3 Junit4的單元測(cè)試用例執(zhí)行順序?yàn)?/p>

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

3 Junit4測(cè)試案例

3.1 Eclipse中使用Junit4

下面是我們?cè)贓clipse中新建的一個(gè)用來(lái)做測(cè)試的一個(gè)計(jì)算器類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 : 死循環(huán)

? ? public void squareRoot(int n)

? ? {

? ? ? ? for (; ;) ;?

? ? }

? ? // 將結(jié)果清零

? ? public void clear()

? ? {

? ? ? ? result = 0;

? ? }

? ? public int getResult()

? ? {

? ? ? ? return result;

}

}

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

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

點(diǎn)擊“下一步”,進(jìn)入勾選測(cè)試方法的頁(yè)面,根據(jù)情況選擇需要做測(cè)試的單元,如圖:

點(diǎn)擊“完成”。Eclipse會(huì)自動(dòng)幫我們生成Junit4的測(cè)試框架,如下:

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

? ? }

}

第四步:編寫我們的測(cè)試內(nèi)容。(在Calculator類中預(yù)留的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());

}

}

如下:運(yùn)行改單元測(cè)試案例,可以查看測(cè)試結(jié)果:

3.2 Gradle中集成Junit4單元測(cè)試

第一步:按照gradle項(xiàng)目目錄結(jié)構(gòu)新建一個(gè)“gradle-junit”的項(xiàng)目工程,該工程下包含標(biāo)示gradle項(xiàng)目的build.gradle文件。并將我們剛才編寫的Calculator類和它的單元測(cè)試案例類CalculatorTest放在對(duì)應(yīng)的目錄中。

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

第三步:執(zhí)行g(shù)radle命令。命令行模式下,進(jìn)入我們剛才仙劍的“gradle-junit”工程所在的目錄,執(zhí)行g(shù)radle test命令。等待gradle構(gòu)建完畢,從窗口可以看到本次執(zhí)行的結(jié)果,如下:

結(jié)果分析解讀:

1. 執(zhí)行testSquareRoot(死循環(huán))測(cè)試失敗,并報(bào)TestTimedOutException異常;

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

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

4. 點(diǎn)擊紅色標(biāo)記的包或者類可以進(jìn)入相應(yīng)的鏈接頁(yè)面,查看更詳細(xì)的結(jié)果。例如點(diǎn)擊下面的CalculatorTest鏈接可以查看測(cè)試失敗單元測(cè)試案例原因:

TestTimedOutException:test timed out after 1000 milliseconds

以及測(cè)試類的一個(gè)測(cè)試結(jié)果類的小結(jié),如下圖:

(可以看見(jiàn)執(zhí)行testSquareRoot測(cè)試任務(wù)花費(fèi)1.010s,超過(guò)我們?cè)O(shè)定的閥值1s;

并且testMultiply測(cè)試任務(wù)變色。)

4 單元測(cè)試代碼覆蓋率

在做單元測(cè)試時(shí),代碼覆蓋率常常被拿來(lái)作為衡量測(cè)試好壞的指標(biāo),甚至,有用代碼覆蓋率來(lái)考核測(cè)試任務(wù)完成情況,比如代碼覆蓋率必須達(dá)到80%或90%。正確理解代碼覆蓋率,對(duì)我們?cè)O(shè)計(jì)案例覆蓋代碼很有必要,因?yàn)閮H僅使用代碼覆蓋率來(lái)衡量,有利也有弊。

4.1 語(yǔ)句覆蓋

語(yǔ)句覆蓋就是度量被測(cè)試代碼中每個(gè)可執(zhí)行語(yǔ)句是否被執(zhí)行到了,簡(jiǎn)單的說(shuō)就是只統(tǒng)計(jì)能夠執(zhí)行的代碼被執(zhí)行了多少行。語(yǔ)句覆蓋常常被人指責(zé)為“最弱的覆蓋”,它只管覆蓋代碼中執(zhí)行語(yǔ)句,卻不考慮各種分支的組合等等。例如:

測(cè)試代碼如下:

int foo (int a,int b)

{

return a/b;

}

假如我們?cè)O(shè)計(jì)如下的測(cè)試案例:

TeseCase:a = 10,b = 5

雖然代碼覆蓋率到達(dá)了100%,并且所有的測(cè)試案例都通過(guò)了。然而遺憾的是,我們的語(yǔ)句覆蓋率到達(dá)了所謂的100%,但是卻沒(méi)有發(fā)現(xiàn)最賤的bug,比如當(dāng)我們?nèi)=0時(shí),會(huì)拋出異常。再如下面的例子:

int foo (int a, int b)

{

int result = 0

if (a < 10) {

result += 1;

}

if (b < 10) {

result += 10;

}

return resule;

}

設(shè)計(jì)的測(cè)試案例如下:

TeseCase:a = 5, b = 5

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

4.2 判定覆蓋和條件覆蓋

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

int foo (int a, int b)

{

if (a < 10 || b < 10)

{

return 0; //分支一

}else {

return 1;//分支二

}

}

設(shè)計(jì)判定覆蓋案例時(shí),我們只需考慮判定結(jié)果為true和false兩種情況,我們?cè)O(shè)計(jì)如下的案例就能達(dá)到判定覆蓋率為100%:

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

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

從設(shè)計(jì)條件的邏輯上出發(fā),TeseCase2的設(shè)計(jì)與TeseCase1的在邏輯上完全的互斥互逆的,簡(jiǎn)單理解為除了分支一的情況,就只剩下分支二了。

設(shè)計(jì)條件覆蓋案例時(shí),我們需要考慮判定中的每個(gè)條件表達(dá)結(jié)果,為了覆蓋率達(dá)到100%,我們?cè)O(shè)計(jì)如下的案例:

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

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

由此可見(jiàn),條件覆蓋不是講判定中的每一個(gè)田間表達(dá)式的結(jié)果進(jìn)行排列組合,而只要每個(gè)條件表達(dá)式的結(jié)果為true和false測(cè)試到了就OK了。說(shuō)白了,條件覆蓋就是不考慮邏輯分支僅覆蓋條件分支,執(zhí)行條件分支語(yǔ)句的覆蓋。因此,我們得出這樣的推論:完全的條件覆蓋并不能覆蓋保證完全的判定覆蓋。

4.3 路徑覆蓋

對(duì)于路徑覆蓋的理解,我們?cè)O(shè)計(jì)如下的案例:

int foo (int a, int b)

{

int result = 0

if (a < 10) {

result += 1;

}

if (b < 10) {

result += 10;

}

return resule;

}

測(cè)試案例:

TsseCase1:a = 5, b = 5

TeseCase2:a = 5, b = 15

TeseCase3:a = 15, b = 5

TeseCase4:a = 15, b = 15

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

5 Jacoco單元測(cè)試覆蓋率

Jacoco專門用于統(tǒng)計(jì)單元測(cè)試覆蓋率的,在gradle中和‘java’插件一樣使用時(shí)直接引用,并添加版本即可:

apply plugin: 'jacoco'

jacoco{? toolVersion = "0.7.1.201405082137"? }

運(yùn)行jacoco也非常簡(jiǎn)單,因?yàn)樗蕾囉趩卧獪y(cè)試的完成,所以執(zhí)行完test任務(wù)后,可以緊接著執(zhí)行g(shù)radle jacoco命令,執(zhí)行成功,也會(huì)生成一個(gè)類似單元測(cè)試的html結(jié)果報(bào)告,非常直觀的可以看到統(tǒng)計(jì)數(shù)據(jù)的結(jié)果。

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

源碼如下:

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;

? ? }? ?

}

下面是設(shè)計(jì)的單元測(cè)試案例:

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

//簡(jiǎn)單的語(yǔ)句覆蓋

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

? ? }*/? ?

}

運(yùn)行g(shù)radle test jacoco,構(gòu)建完成后可以查看覆蓋率報(bào)告,如下:

顯示分支覆蓋率(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;

? ? ? ? ? ? }

? ? ? ? }

? ? }

}

下面是設(shè)計(jì)的單元測(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));

? ? }? ?

}

執(zhí)行后結(jié)果如下,還可以進(jìn)入鏈接查看詳情

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

}? ?

}

執(zhí)行后結(jié)果如下:

點(diǎn)擊鏈接還可以查詢沒(méi)有覆蓋到的分支:

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

所以增加如下單元測(cè)試案例:

@Test

public void test_max_2_1_3() {

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

}

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容