为什么Jacoco分行覆盖率报告会说a && b && c是否实际上是6个分行?

问题描述

我的公司要求新代码必须达到90%的测试覆盖率,而对于Java代码,我正在使用gradle jacoco插件,这很好。但是,当分支的数量开始呈指数增长(夸张的,可能是几何增长)时,很难将分支覆盖率提高到90%。

这是一个非常人为的例子:

public class Application {
    public static void test(boolean a,boolean b) {
        if (a && b) {
            System.out.println("true!");
        } else {
            System.out.println("false!");
        }
    }
}

测试:

public class ApplicationTests {
    @Test
    public void test() {
        Application.test(true,true);
        Application.test(false,false);
    }
}

以下是覆盖率报告的样子:

Test Report Screenshot

它还说我错过了4个分支中的1个,换句话说,我已经覆盖了4个分支中的3个(75%的分支覆盖率)。

如果我在这里增加布尔值的数量,则分支的数量似乎是n * 2,其中n是布尔值的数量。因此3(a,b,c)变成6个分支,而10个变成20个分支。所以我想我不明白在这种情况下有6或20个分支的含义。

要满足这个问题-我可以

A)将jacoco配置为更直观,并将if / else条件视为始终具有2个分支(分支1是if执行时​​的分支,分支2是else执行时的分支)-子表达式的惰性执行可以是跟踪为线路覆盖率或其他内容

B)为了更完整地解释为什么它说这些if / else有4、6、20个分支,并将2、3、10个布尔值组合成1个表达式。

编辑-澄清混乱的来源:

  1. 在只有2个电话的情况下,我如何覆盖3个分支?
  2. 为什么示例中3个布尔值(a && b && c)的分支数量达到6个分支,而10个布尔值(a && b && c && .. && j)却达到20个分支?

如果每个布尔值为true或false,然后我同时调用两种状态的函数,那么在这里我怎么没有得到100%的分支覆盖率?我想念一些东西。

解决方法

在编写类似if(a && b)的条件时,在运行测试用例时,它将适用于下面提到的所有四个方案。

{
    applicationVersion: '4194309',application: 'com.domain.app',scope: '*',authorizedEntity: '913674269572',rel: { topics: { topicName: { addDate: '2020-08-19' } } },appSigner: 'ae7cbSomeHash23jsdfff34ac7ffd',platform: 'ANDROID'
} 

因此,您需要调用此方法四次以涵盖 result a b true true true false false true false true false false false false

您还可以创建一些实用程序类,该实用程序类将根据参数数量生成这些方案。

,

现有答案部分解释了给定的示例,但我想在这里添加一个更一般的观点:Jacoco 分析字节码,分支覆盖率仅计算其中的二进制条件语句(分支)的目标。

给定上面的例子,但有三个变量,我们得到 6 个分支。

snippet

查看字节码我们看到短路操作符被翻译成三个ifeq,代表分支语句。他们每个人都有两个可能的目标,一共六个。

  public static void test(boolean,boolean,boolean);
    Code:
       0: iload_0
       1: ifeq          23
       4: iload_1
       5: ifeq          23
       8: iload_2
       9: ifeq          23
      12: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      15: ldc           #22                 // String true!
      17: invokevirtual #24                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      20: goto          31
      23: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      26: ldc           #30                 // String false!
      28: invokevirtual #24                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      31: return

如何获得全覆盖可以在对应的控制流图中看到:第一个测试用例test(true,true,true)沿着顶部的路径,覆盖了三个分支。对于剩下的分支,我们还需要三个测试用例,每个都在短路算子处进行另一个“出口”。

cfg

100% 分支覆盖率所需的测试用例不会随着条件中子表达式的数量呈指数增长 - 事实上它是线性的。然而,要求覆盖子条件的所有可能组合(这里:a、b 和 c 的值)被称为多条件覆盖(wikipedia:coverage)。 Jacoco 无法检查,因为字节码只知道二进制条件语句。

,

所以我想我现在想出了为什么分支数量等于n * 2的原因,其中n是if()条件内的布尔表达式的数量。

每个布尔表达式都是其自己的分支,因此在此示例中,如果我们有a && b && c,则有3个不同的表达式,每个表达式具有2个状态,因此有6个分支。要覆盖所有6个分支,测试必须确保在正确和错误状态下评估每个变量。关键部分是必须对每个表达式进行求值,在某些情况下,这并不是因为Java中的惰性求值。

public class Application {
    public static void test(boolean a,boolean b,boolean c) {
        if (a && b && c) {
            System.out.println("true!");
        } else {
            System.out.println("false!");
        }
    }
}

对于示例if (a && b && c),当传递所有a的{​​{1}},bc值时,实际上一次执行覆盖了3个分支。但是,如果全部以true的形式传递,则它仅覆盖一个分支,因为false的错误和惰性评估不会检查bc

在这种情况下,要有效覆盖所有6个分支,必须多次调用test函数以达到100%的分支覆盖率。

a
,

在此示例中,您实际上只需要进行3次测试即可获得100%的覆盖率。在两者均为假的情况下进行测试不会提供任何额外的保障。凭直觉,这应该是有道理的。除非它的至少一个参数为false,否则您希望它显示为true。

构造代码的方式也会影响分支的数量。如果要求做一件事,那么当所有事情都为真时执行,而当其中任何一个都不为假时,则可以执行以下操作:

if (Stream.of(a,b).reduce(Boolean::logicalAnd).get(){
   System.out.println("true");
} else {
   System.out.println("false");
}

在一个只有两个输入的人为示例中,它看起来有点愚蠢。如果在实际环境中有两个以上的输入,则可能更有意义。例如,您可能有一个List<ValidationRule>之类的东西,并且每个元素都计算一个布尔值。我不会再说太多了,因为它超出了您最初提出的问题的范围,但这可能值得考虑。