Kotlin JaCoCo-非法ClassFormatException.请提供原始的未插入指令的类



我正在尝试获取Android应用程序模块的测试覆盖率报告,并执行testVariantBuildTypeUnitTest任务。

尽管所有测试都通过了,但TEST classNameTest.xml文件包含以下错误消息。此错误仅适用于包含从Kotlin调用Java的方法。这个问题有什么解决办法吗?

**Caused by: java.lang.IllegalStateException: Cannot process instrumented class... Please supply original non-instrumented classes.
at org.jacoco.agent.rt.internal_f3994fa.core.internal.instr.InstrSupport.assertNotInstrumented(InstrSupport.java:238)
at org.jacoco.agent.rt.internal_f3994fa.core.internal.instr.ClassInstrumenter.visitField(ClassInstrumenter.java:56)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassVisitor.visitField(ClassVisitor.java:339)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassReader.readField(ClassReader.java:1111)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassReader.accept(ClassReader.java:713)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassReader.accept(ClassReader.java:401)
at org.jacoco.agent.rt.internal_f3994fa.core.instr.Instrumenter.instrument(Instrumenter.java:90)
at org.jacoco.agent.rt.internal_f3994fa.core.instr.Instrumenter.instrument(Instrumenter.java:108)

我们有多模块的android项目,因此我们使用这个自定义的Jacobo任务来获得覆盖率报告:

project.afterEvaluate {
(android.hasProperty('applicationVariants')
? android.'applicationVariants'
: android.'libraryVariants')
.all { variant ->
def variantName = variant.name
def unitTestTask = "test${variantName.capitalize()}UnitTest"
def jacocoReportName = unitTestTask + project.name
tasks.create(name: jacocoReportName, type: JacocoReport, dependsOn: [
"$unitTestTask"
]) {
group = "Reporting"
description = "Generate Jacoco coverage reports for the ${variantName.capitalize()} build"
reports {
html.enabled = true
xml.enabled = true
}
def fileFilter = [
// data binding
'android/databinding/**/*.class',
'**/android/databinding/*Binding.class',
'**/android/databinding/*',
'**/androidx/databinding/*',
'**/databinding',
'**/BR.*',
// android
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*'
]
def javaClasses = fileTree(dir: variant.javaCompileProvider.get().destinationDir,
excludes: fileFilter)
def kotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/${variantName}",
excludes: fileFilter)
classDirectories.setFrom(files([
javaClasses,
kotlinClasses
]))
def variantSourceSets = variant.sourceSets.java.srcDirs.collect { it.path }.flatten()
sourceDirectories.setFrom(project.files(variantSourceSets))
if (isAndroidLibrary(project)) {
executionData(files([
"${projectDir}/jacoco.exec"
]))
}else{
executionData(files([
"$project.buildDir/jacoco/${project.name}.exec"
]))
}
}
}
}

问候,

解决方案

问题很可能出现在模块级build.gradle文件中。您需要删除testCoverageEnabled或将其设置为false。

android {
...
buildTypes {
release {
minifyEnabled true
}
debug {
// testCoverageEnabled true 
minifyEnabled false
}
}

解释和参考

  • 首先,多年来,我一直在努力解决JaCoCo在android上的单元测试覆盖问题,并在AGP(android Gradle插件(团队增强AGP时多次遇到这些问题(例如类似问题(,因此该解决方案可能会在AGP的未来版本中发生变化。

  • AGP 4.1发布后,AGP团队改变了插件处理单元测试的方式。我在这里向AGP团队提出了一个输入链接描述问题,他们解释说,插件(AGP 4.1+(使用自己的工具来获得覆盖范围,这无疑与JaCoCo不兼容。正是在这个问题上,解决方案向我强调

注意1:DSL文档中没有记录此问题!

注2:还应该注意的是,当我写这篇AGP 7时,它正在开发中,出现了同样的问题。

注意事项

此解决方案将导致生成检测测试覆盖率的问题,因为AGP生成检测测试的覆盖率(以.ec文件的形式(,这意味着要获得检测测试覆盖,必须具有testCoverageEnabled true。作为一种变通方法,您可以:

  • 将单元测试放在版本构建变体中
  • 将插入指令的测试放入调试构建变体中。仍然可以将覆盖范围合并为一个报告:例如:
task jacocoCombinedUnitTestAndroidTestReport(type: JacocoReport) {
group = "Reporting"
reports {
xml.enabled = true
html.enabled = true
}
def mainSrc = "$project.projectDir/src/main/java"
def releaseKotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/release", excludes:["com/example/ui**"])
def debugKotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug/com/example/ui/")
def releaseJavaClasses = fileTree(dir: "${buildDir}/intermediates/javac/release", excludes: ["com/example/ui**"])
def debugJavaClasses = fileTree(dir: "${buildDir}/intermediates/javac/debug/com/example/ui/" )
def mainClasses = releaseKotlinClasses + debugKotlinClasses
def javaClasses = releaseJavaClasses + debugJavaClasses
sourceDirectories.from = files([mainSrc])
classDirectories.from = files([mainClasses, javaClasses])
executionData.from = fileTree(dir: buildDir, include: ["jacoco/testReleaseUnitTest.exec",
"outputs/code_coverage/debugAndroidTest/connected/**.ec"])
}
  • 这假设您在UI目录中有任何UI代码
  • 您包括除ui目录之外的所有发布类
  • 您只包含您的ui调试类

请注意,这仅在撰写此答案时有效,希望谷歌将来能解决此问题。

最新更新