【问题标题】:Incorrect Jacoco code coverage for Kotlin coroutineKotlin 协程的 Jacoco 代码覆盖率不正确
【发布时间】:2018-11-26 16:31:50
【问题描述】:

我正在使用 Jacoco 进行单元测试代码覆盖。 Jacoco 生成的报告显示,在我的 Kotlin 代码中,很少有分支被遗漏。我注意到 协程代码 和它之后的代码,根据 Jacoco 没有正确覆盖。我不确定是因为协程还是其他原因。 在使用 IntelliJ 代码覆盖率 运行我的单元测试时,我的 Kotlin 类显示 100% 覆盖率。

我不知道为什么 Jacoco 的报道较少。我已经使用 Spock (Groovy) 编写了我的单元测试。

请参考以下图片:

错过的分支:

原始代码:

【问题讨论】:

    标签: kotlin code-coverage spock kotlin-coroutines jacoco


    【解决方案1】:

    类似于“Why is JaCoCo not covering my String switch statements?”:

    JaCoCo 执行字节码分析,而不是源代码。 Example.kt与kotlinc 1.3.10的编译

    package example
    
    fun main(args: Array<String>) {
        kotlinx.coroutines.runBlocking { // line 4
        }
    }
    

    产生两个文件ExampleKt.class和ExampleKt$main$1.class,最后一个的字节码(javap -v -p ExampleKt$main$1.class)包含方法invokeSuspend(Object)

      public final java.lang.Object invokeSuspend(java.lang.Object);
        descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
        flags: ACC_PUBLIC, ACC_FINAL
        Code:
          stack=3, locals=4, args_size=2
             0: invokestatic  #29                 // Method kotlin/coroutines/intrinsics/IntrinsicsKt.getCOROUTINE_SUSPENDED:()Ljava/lang/Object;
             3: astore_3
             4: aload_0
             5: getfield      #33                 // Field label:I
             8: tableswitch   { // 0 to 0
                           0: 28
                     default: 53
                }
            28: aload_1
            29: dup
            30: instanceof    #35                 // class kotlin/Result$Failure
            33: ifeq          43
            36: checkcast     #35                 // class kotlin/Result$Failure
            39: getfield      #39                 // Field kotlin/Result$Failure.exception:Ljava/lang/Throwable;
            42: athrow
            43: pop
            44: aload_0
            45: getfield      #41                 // Field p$:Lkotlinx/coroutines/CoroutineScope;
            48: astore_2
            49: getstatic     #47                 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit;
            52: areturn
            53: new           #49                 // class java/lang/IllegalStateException
            56: dup
            57: ldc           #51                 // String call to 'resume' before 'invoke' with coroutine
            59: invokespecial #55                 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
            62: athrow
          LineNumberTable:
            line 4: 3
            line 5: 49
    

    与源文件的第 4 行关联并包含分支(ifeq、tableswitch)。

    虽然截至今​​天最新的 JaCoCo 版本 (0.8.2) 具有针对各种编译器生成的工件的过滤器,例如 switch 语句中的 String,但 Kotlin 编译器为协程生成的字节码没有被过滤。更改日志可以在 https://www.jacoco.org/jacoco/trunk/doc/changes.html 看到。除此之外,https://www.jacoco.org/research/index.html 还有 presentation about bytecode pattern matching 显示/解释了许多编译器生成的工件。


    您在 IntelliJ IDEA 中看到的 100% - 只是线路覆盖率,因此您试图比较两个完全不同的事物。作为证明 - 这是 IntelliJ IDEA 的屏幕截图,它显示了 100% 的线路覆盖率,但只执行了 if 的一个分支(其中 args.size &gt;= 0 的计算结果为 true)

    这里是JaCoCo报告执行相同源文件的对应截图

    上升到包级别,您可以看到 100% 的线路覆盖率,但 50% 的分支覆盖率

    然后通过第一个链接ExampleKt.main.new Function2() {...} 进入类级别,您可以再次看到方法invokeSuspend(Object) 贡献了错过的分支


    更新 (29/01/2019)

    JaCoCo version 0.8.3 对 Kotlin 编译器添加的用于挂起 lambda 和函数的分支进行过滤:

    【讨论】:

    • 感谢您的详细解释。虽然,我已经弄清楚了这一点,因此绕过了对这些 kotlin 编译器生成的带有 Function2 关键字的代码的覆盖。我希望这是正确的方法,因为我不应该为编译器生成的代码的代码覆盖率而烦恼。你有什么看法?
    • @SahilChhabra 我怀疑这是正确的,因为据我所知 - runBlocking { /* body, e.g. println("hello") */ } 的“主体”在方法 invokeSuspend 以及其他分支中。
    • 我明白你的意思。这似乎是正确的。但是那我应该如何覆盖那些额外的行或分支的invokeSuspend。
    • @SahilChhabra 暂时你需要忍受这个限制。您可以关注行覆盖率 - 引用eclemma.org/jacoco/trunk/doc/counters.html“当分配给该行的至少一条指令已被执行时,该源行被视为已执行。”如上所示,指标“线路覆盖率”与 IntelliJ IDEA 显示的指标相同,并且该指标的 100% 完全可达。
    【解决方案2】:

    Jacoco 0.8.3 版本修复了它,它已于昨天 1 月 24 日发布。

    完整的变更日志可以在这里找到:https://github.com/jacoco/jacoco/releases

    【讨论】:

      猜你喜欢
      • 2018-07-24
      • 1970-01-01
      • 2016-11-11
      • 2019-01-02
      • 2012-11-02
      • 1970-01-01
      • 2014-11-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多