单元测试powerMock覆盖要点


前言

单元测试,就是对某一段细粒度的Java代码的逻辑测试。代码块一般指一个Java 方法本身,所有外部依赖都需要mock掉,仅关注代码逻辑本身。
需要注意,单测的一个大前提就是需要清楚的知道自己要测试的程序块所预期的输入输出,然后根据这个预期和程序逻辑来书写case。

覆盖类型

1、行覆盖 Statement Coverage

行覆盖(又叫语句覆盖)就是通过设计一定量的测试用例,保证被测试的方法每一行代码都会被执行一遍。

路径覆盖是最弱的覆盖方式。

实例:

  1. public Integer fun3(Integer a, Integer b, Integer x) {
  2. if (a > 1 && b == 0) {
  3. x = x + a;
  4. }
  5. if (a == 2 || x > 1) {
  6. x += 1;
  7. }
  8. return x;
  9. }

本例仅需要一个case,即可实现行覆盖。test case 如下:

a b x 预期结果
TC1 2 0 3 6
  1. @Test
  2. public void testFun3StatementCoverage(){
  3. Integer res = demoService.fun3(2,0,3);
  4. Assert.assertEquals(6,res.intValue());
  5. }

这个用例就可以保证所有的行都被执行。

但是仅仅有这一个用例的话,对这个方法的测试就是非常脆弱的。

  1. public Integer fun4(Integer a, Integer b, Integer x) {
  2. if (a > 1 || b == 0) {
  3. x += a;
  4. }
  5. if (a == 2 || x > 1) {
  6. x += 1;
  7. }
  8. return x;
  9. }

行覆盖是一个最基础的覆盖方式,但是也是最薄弱的,如果完全依赖行覆盖,那不小心就会被开除

2、判定覆盖 / 分支覆盖 (Decision Coverage/Branch Coverage)

  1. public Integer fun3(Integer a, Integer b, Integer x) {
  2. if (a > 1 && b == 0) {
  3. x = x + a;
  4. }
  5. if (a == 2 || x > 1) {
  6. x += 1;
  7. }
  8. return x;
  9. }

判定覆盖的含义就是代码里每一个判定都要走一次true,一次false。依然用上面的代码,想要实现判定覆盖,需要以下case

a b x 预期结果
TC2 2 0 1 4
TC3 3 1 1 1
  1. @Test
  2. public void testFun3DecisionCoverage(){
  3. Integer res = demoService.fun3(2,0,1);
  4. Assert.assertEquals(4,res.intValue());
  5. res = demoService.fun3(3,1,1);
  6. Assert.assertEquals(1,res.intValue());
  7. }

这两个用例可以保证判定 A: (a > 1 || b == 0) 和判定B: (a == 2 || x > 1) 分别都取一次true 和false:

tc2 时, A,B均为true;tc3时,A,B均为false。

可以看出分支覆盖依然有明显缺陷,并没有覆盖到 A: true B: false 和 A:false B:true的情况。

3、条件覆盖 Condition Coverage

  1. @Test
  2. public void testFun3ConditionCoverage(){
  3. Integer res = demoService.fun3(2,0,3);
  4. Assert.assertEquals(6,res.intValue());
  5. res = demoService.fun3(0,1,0);
  6. Assert.assertEquals(0,res.intValue());
  7. }

条件覆盖和判定覆盖类似,不过判定覆盖着眼于整个判定语句,而条件覆盖则着眼于某个判断条件。

条件覆盖需要保证每个判断条件的true false都要覆盖到,而不是整个判定语句。

例如,判定A (a > 1 || b == 0) ,只需要整个判定表达式分别取一次真假即可满足判定覆盖,而要满足条件覆盖,则需要判断条件 (a>1) 和 (b==0) 分别都取一次true false才算满足。

依然采用同样的代码,要想实现条件覆盖,则需要:

a b x 预期结果
TC4 2 0 3 6
TC5 0 1 0 0
  1. @Test public void testFun3ConditionCoverage(){ Integer res = demoService.fun3(2,0,3); Assert.assertEquals(6,res.intValue()); res = demoService.fun3(0,1,0); Assert.assertEquals(0,res.intValue()); }

这两个用例可以保证 (a > 1) (b==0) (a == 2) (x > 1) 四个条件都分别取true false。

很明显可以发现,这玩意儿依然是不全面的,这个例子里条件覆盖和判定覆盖存在同样的问题,覆盖的不够全面。

4、路径覆盖 Path Coverage

public Integer fun3(Integer a, Integer b, Integer x) {
        if (a > 1 && b == 0) {
            x = x + a;
        }
        if (a == 2 || x > 1) {
            x += 1;
        }
        return x;
    }

路径覆盖这个顾名思义就是覆盖所有可能执行的路径。

为了方便理解,这里先把流程图画出来。

424424-20191029120109680-1681253282.png

红色代表一段路径。

首先梳理所有路径:

路径1:1–>3–>5;

路径2:1–>2–>5;

路径3:1–>3–>4;

路径4:1–>2–>4;

路径覆盖就是需要设计用例,将所有的路径都走一遍。

设计以下用例:

a b x 预期结果 经过路径
TC6 0 1 0 0 1
TC7 3 0 -3 0 2
TC8 2 1 3 4 3
TC9 2 0 3 6 4
@Test
    public void testFun3PathCoverage(){
        Integer res = demoService.fun3(0,1,0);
        Assert.assertEquals(0,res.intValue());
        res = demoService.fun3(3,0,-3);
        Assert.assertEquals(0,res.intValue());
        res = demoService.fun3(2,1,3);
        Assert.assertEquals(4,res.intValue());
        res = demoService.fun3(2,0,3);
        Assert.assertEquals(6,res.intValue());
    }