Java 为什么Jacoco分支机构的覆盖报告会说(a&;b&;c)实际上是6家分支机构?

Java 为什么Jacoco分支机构的覆盖报告会说(a&;b&;c)实际上是6家分支机构?,java,code-coverage,jacoco,Java,Code Coverage,Jacoco,我的公司要求我们对新代码的测试覆盖率达到90%,而对于Java代码,我使用的是gradle jacoco插件,这很好;然而,当分支数量开始呈指数增长时(夸张地说,这可能是几何增长),分支覆盖率很难提高到90% 下面是一个非常做作的例子: public class Application { public static void test(boolean a, boolean b) { if (a && b) { System.out

我的公司要求我们对新代码的测试覆盖率达到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);
    }
}
以下是覆盖率报告的内容:

它还说我错过了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个布尔值组合成一个表达式

编辑--澄清混淆的来源:

  • 在这个示例中,当只有2个调用时,我如何覆盖3个分支
  • 为什么示例中3个布尔值
    (a&&b&&c)
    的分支数变为6个分支,10个布尔值
    (a&&b&&c&&j)
    变为20个分支

  • 如果每个布尔值要么为真,要么为假,然后我用这两种状态调用函数,我怎么没有得到100%的分支覆盖率呢?我遗漏了一些东西。

    当您编写if(a&&b)这样的条件时,在运行测试用例时,它将与下面提到的所有四种场景一起运行

    result a     b
    true   true  true
    false  false true
    false  true  false
    false  false false
    
    因此,您需要调用此方法四次以覆盖100%的覆盖率


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

    因此,我想现在已经找到了分支数量等于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
    b
    c
    值时,所有
    true
    ,这实际上在一次执行中覆盖了3个分支。但是,如果您以
    false
    的形式通过所有,则它只覆盖一个分支,因为
    b
    c
    由于
    a
    为false和延迟求值,因此从不检查

    在这种情况下,为了有效地覆盖所有6个分支,必须至少调用4次测试函数,以实现100%的分支覆盖率

    /*
     * Using ? to indicate it can be true or false,
     * it won't matter because the variable would never be read due to lazy evaluation.
     */
    Application.test(true, true, true);  // +3 branches covered
    Application.test(true, true, false); // +1 branch covered
    Application.test(true, false, ?);    // +1 branch covered
    Application.test(false, ?, ?);       // +1 branch covered
                                         // total: 6 branches
    

    在本例中,您实际上只需要3个测试就可以获得100%的覆盖率。当两者都为false时测试用例不会提供任何额外的覆盖范围。凭直觉,这应该是有道理的。您希望它打印为true,除非至少有一个参数为false

    构造代码的方式也会影响分支的数量。如果要求做一件事,那么当所有这些都是真的,而当其中任何一个都是假的,那么您可以通过两个分支来完成:

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

    在一个只有两个输入的人为示例中,它看起来有点傻。如果在实际上下文中有两个以上的输入,那么它就更有意义了。例如,您可以有一个类似于
    列表的东西,每个元素计算一个布尔值。我不想多说,因为这超出了您最初问题的范围,但可能值得考虑。

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

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

    查看字节码,我们看到短路运算符被转换为三个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)
    沿着顶部的路径,覆盖三个分支。对于其余的分支,我们还需要三个测试用例,每个测试用例都在短路操作符处“退出”


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

    分析是正确的,该函数实现了四种不同情况的行为。事实上,其中三种情况的行为是(或至少是当前!)同样的也没关系。@jonrsharpe谢谢,但我还是不明白——对于2个布尔函数,分支计数是4,这很有道理。但是为什么3个布尔函数被认为是6个分支,10个布尔函数被认为是20个呢?还有,当我只调用了2次函数时,我是如何涵盖3种情况的?如果这是真的,那么为什么不呢