Kotlin协程的Jacoco代码覆盖率不正确

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin协程的Jacoco代码覆盖率不正确相关的知识,希望对你有一定的参考价值。

我正在使用Jacoco进行单元测试代码覆盖。 Jacoco生成的报告显示我的Kotlin代码中缺少一些分支。根据Jacoco的说法,我注意到协程代码和之后的代码没有得到适当的覆盖。我不确定这是因为协程还是别的什么。在使用IntelliJ代码覆盖率运行我的单元测试时,我的Kotlin类显示100%的覆盖率。

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

请参考以下图片:

错过的分支机构:enter image description here

enter image description here

原始代码:enter image description here

答案

与“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.classExampleKt$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行相关联并包含分支(ifeqtableswitch)。

虽然最新的JaCoCo版本(0.8.2)具有针对各种编译器生成的工件的过滤器,例如String语句中的switch,但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 >= 0评估为true

intellij

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

jacoco source level

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

jacoco package level

然后通过第一个链接ExampleKt.main.new Function2() {...}下到班级你可以再次看到invokeSuspend(Object)方法贡献错过的分支

jacoco class level


更新(29/01/2019)

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

before

after

另一答案

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

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

以上是关于Kotlin协程的Jacoco代码覆盖率不正确的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 协程协程的挂起和恢复 ① ( 协程的挂起和恢复概念 | 协程的 suspend 挂起函数 )

Kotlin 协程协程的挂起和恢复 ① ( 协程的挂起和恢复概念 | 协程的 suspend 挂起函数 )

Kotlin协程源码分析-协程的启动

使用协程的 Kotlin/Native 多线程

Kotlin 协程的作用域构建器 coroutineScope与runBlocking 与supervisorScope,协程同步运行,协程挂掉的时候其他协程如何不被挂掉。

Kotlin协程的原理,没有说得比AndroidDeveloper官方更显浅的了