Kotlin 协程的 Jacoco 代码覆盖率不正确
Incorrect Jacoco code coverage for Kotlin coroutine
我正在使用 Jacoco 进行单元测试代码覆盖。 Jacoco 生成的报告显示 在我的 Kotlin 代码 中遗漏了几个分支。我注意到 coroutine code 和它后面的代码,根据 Jacoco 没有被正确覆盖。我不确定是因为协程还是其他原因。
运行 我的单元测试 IntelliJ 代码覆盖率 我的 Kotlin class 显示 100% 覆盖率.
我不知道为什么 Jacoco 显示的覆盖率较低。我已经使用 Spock (Groovy) 编写了我的单元测试。
请参考下图:
错过的分支:
原代码:
类似于“”:
JaCoCo 执行字节码分析,而不是源代码。 Example.kt
与 kotlinc 1.3.10
的编译
package example
fun main(args: Array<String>) {
kotlinx.coroutines.runBlocking { // line 4
}
}
结果在两个文件 ExampleKt.class
和 ExampleKt$main.class
中,最后一个 (javap -v -p ExampleKt$main.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 And among others at https://www.jacoco.org/research/index.html there is also presentation about bytecode pattern matching 可以看到变更日志 shows/explains 许多编译器生成的工件。
您在 IntelliJ IDEA 中看到的 100% 只是行覆盖率,因此您正在尝试比较两个完全不同的事物。作为证明——这里是 IntelliJ IDEA 的屏幕截图,它显示了 100% 的行覆盖率,但只执行了 if
的一个分支(其中 args.size >= 0
计算为 true
)
这里是执行相同源文件的 JaCoCo 报告的相应屏幕截图
上升到包级别,您可以看到 100% 的行覆盖率,但 50% 的分支覆盖率
然后通过第一个 link ExampleKt.main.new Function2() {...}
下降到 class 级别,您可以再次看到该方法 invokeSuspend(Object)
贡献了遗漏的分支
更新 (29/01/2019)
JaCoCo version 0.8.3 具有 Kotlin 编译器为暂停 lambda 和函数添加的分支过滤器:
Jacoco 0.8.3 版本修复,已于昨天 1 月 24 日发布。
可在此处找到完整的更改日志:https://github.com/jacoco/jacoco/releases
我正在使用 Jacoco 进行单元测试代码覆盖。 Jacoco 生成的报告显示 在我的 Kotlin 代码 中遗漏了几个分支。我注意到 coroutine code 和它后面的代码,根据 Jacoco 没有被正确覆盖。我不确定是因为协程还是其他原因。 运行 我的单元测试 IntelliJ 代码覆盖率 我的 Kotlin class 显示 100% 覆盖率.
我不知道为什么 Jacoco 显示的覆盖率较低。我已经使用 Spock (Groovy) 编写了我的单元测试。
请参考下图:
错过的分支:
原代码:
类似于“
JaCoCo 执行字节码分析,而不是源代码。 Example.kt
与 kotlinc 1.3.10
package example
fun main(args: Array<String>) {
kotlinx.coroutines.runBlocking { // line 4
}
}
结果在两个文件 ExampleKt.class
和 ExampleKt$main.class
中,最后一个 (javap -v -p ExampleKt$main.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 And among others at https://www.jacoco.org/research/index.html there is also presentation about bytecode pattern matching 可以看到变更日志 shows/explains 许多编译器生成的工件。
您在 IntelliJ IDEA 中看到的 100% 只是行覆盖率,因此您正在尝试比较两个完全不同的事物。作为证明——这里是 IntelliJ IDEA 的屏幕截图,它显示了 100% 的行覆盖率,但只执行了 if
的一个分支(其中 args.size >= 0
计算为 true
)
这里是执行相同源文件的 JaCoCo 报告的相应屏幕截图
上升到包级别,您可以看到 100% 的行覆盖率,但 50% 的分支覆盖率
然后通过第一个 link ExampleKt.main.new Function2() {...}
下降到 class 级别,您可以再次看到该方法 invokeSuspend(Object)
贡献了遗漏的分支
更新 (29/01/2019)
JaCoCo version 0.8.3 具有 Kotlin 编译器为暂停 lambda 和函数添加的分支过滤器:
Jacoco 0.8.3 版本修复,已于昨天 1 月 24 日发布。
可在此处找到完整的更改日志:https://github.com/jacoco/jacoco/releases