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í)跳過篮赢,或者說被改元素標(biāo)記的方法在測(cè)試中會(huì)被忽略齿椅。當(dāng)測(cè)試的方法還沒有實(shí)現(xiàn),或者測(cè)試的方法已經(jīng)過時(shí)启泣,或者在某種條件下才能測(cè)試該方法(比如需要一個(gè)數(shù)據(jù)庫聯(lián)接涣脚,而在本地測(cè)試的時(shí)候,數(shù)據(jù)庫并沒有連接)寥茫,那么使用該標(biāo)簽來標(biāo)示這個(gè)方法遣蚀。同時(shí),你可以為該標(biāo)簽傳遞一個(gè)String的參數(shù)纱耻,來表明為什么會(huì)忽略這個(gè)測(cè)試方法芭梯。比如:@lgnore(“該方法還沒有實(shí)現(xiàn)”),在執(zhí)行的時(shí)候弄喘,僅會(huì)報(bào)告該方法沒有實(shí)現(xiàn)玖喘,而不會(huì)運(yùn)行測(cè)試方法。
6. 提供測(cè)試響應(yīng)時(shí)間@Test(timeout=1000)(單位:毫秒)蘑志,如果測(cè)試的運(yùn)行時(shí)間超過指定的毫秒數(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) 這兩用來比較數(shù)組:如果數(shù)組的長度和對(duì)應(yīng)的元素相同,測(cè)這兩個(gè)數(shù)組相等澎媒,否則不等搞乏,也考慮了數(shù)組為空的情況。
2.2 Junit4中常見的幾個(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è)用來做測(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è)試方法的頁面纷跛,根據(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è)跳過了(skipped);
3. 最后最大的亮點(diǎn)在,gradle為我們生成了本次測(cè)試的html頁面報(bào)告文件(…/build/reports/tests/test/index.html)与倡,可以非常直觀的顯示本次構(gòu)建結(jié)果界逛,并且還支持鏈接查看類和我們剛才編寫的單元測(cè)試案例。
4. 點(diǎn)擊紅色標(biāo)記的包或者類可以進(jìn)入相應(yīng)的鏈接頁面纺座,查看更詳細(xì)的結(jié)果息拜。例如點(diǎn)擊下面的CalculatorTest鏈接可以查看測(cè)試失敗單元測(cè)試案例原因:
TestTimedOutException:test timed out after 1000 milliseconds
以及測(cè)試類的一個(gè)測(cè)試結(jié)果類的小結(jié),如下圖:
(可以看見執(zhí)行testSquareRoot測(cè)試任務(wù)花費(fèi)1.010s净响,超過我們?cè)O(shè)定的閥值1s少欺;
并且testMultiply測(cè)試任務(wù)變色。)
4 單元測(cè)試代碼覆蓋率
在做單元測(cè)試時(shí)馋贤,代碼覆蓋率常常被拿來作為衡量測(cè)試好壞的指標(biāo)赞别,甚至,有用代碼覆蓋率來考核測(cè)試任務(wù)完成情況配乓,比如代碼覆蓋率必須達(dá)到80%或90%仿滔。正確理解代碼覆蓋率,對(duì)我們?cè)O(shè)計(jì)案例覆蓋代碼很有必要犹芹,因?yàn)閮H僅使用代碼覆蓋率來衡量崎页,有利也有弊。
4.1 語句覆蓋
語句覆蓋就是度量被測(cè)試代碼中每個(gè)可執(zhí)行語句是否被執(zhí)行到了腰埂,簡單的說就是只統(tǒng)計(jì)能夠執(zhí)行的代碼被執(zhí)行了多少行飒焦。語句覆蓋常常被人指責(zé)為“最弱的覆蓋”,它只管覆蓋代碼中執(zhí)行語句屿笼,卻不考慮各種分支的組合等等牺荠。例如:
測(cè)試代碼如下:
int foo (int a,int b)
{
return a/b;
}
假如我們?cè)O(shè)計(jì)如下的測(cè)試案例:
TeseCase:a = 10,b = 5
雖然代碼覆蓋率到達(dá)了100%,并且所有的測(cè)試案例都通過了驴一。然而遺憾的是休雌,我們的語句覆蓋率到達(dá)了所謂的100%,但是卻沒有發(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è)試案例的語句都執(zhí)行了孝情,包括分支中執(zhí)行的語句鱼蝉,因此語句覆蓋率也達(dá)到了100%,但是從分支邏輯上明顯覆蓋不全。
4.2 判定覆蓋和條件覆蓋
判定覆蓋又稱分支覆蓋箫荡,它是度量程序中沒一個(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的在邏輯上完全的互斥互逆的利术,簡單理解為除了分支一的情況呈野,就只剩下分支二了。
設(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
由此可見橘券,條件覆蓋不是講判定中的每一個(gè)田間表達(dá)式的結(jié)果進(jìn)行排列組合,而只要每個(gè)條件表達(dá)式的結(jié)果為true和false測(cè)試到了就OK了。說白了,條件覆蓋就是不考慮邏輯分支僅覆蓋條件分支率触,執(zhí)行條件分支語句的覆蓋。因此汇竭,我們得出這樣的推論:完全的條件覆蓋并不能覆蓋保證完全的判定覆蓋葱蝗。
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也非常簡單,因?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案例分析(一):語句覆蓋與判定覆蓋
源碼如下:
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();
//簡單的語句覆蓋
@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)擊鏈接還可以查詢沒有覆蓋到的分支:
紅色表示沒有覆蓋到的分支;分析沒有覆蓋到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%的分支代碼覆蓋率帚屉。