单元测试与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%的分支代码覆盖率。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容