You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android多产品风味Kotlin+Hilt项目JaCoCo报错覆盖率为0求助

问题根因

字节码转换导致类不匹配的判断方向正确,但类文件拷贝时机完全错误。
Hilt字节码插桩、Kotlin协程内部类生成、AGP自身的类转换(desugar、多flavor变体类合并、debug版字节码埋点等)都不会在compileDebugJavaWithJavac任务结束时终止,你在Hilt任务执行前拷贝的类属于插桩前的中间产物,和测试运行时实际加载的插桩后类结构完全不一致,用中间产物匹配运行时生成的覆盖率exec数据,必然报类不匹配错误,最终覆盖率统计为0。
另外原有配置的Hilt任务匹配正则存在覆盖漏洞,没有命中Hilt ASM插桩、Kotlin类转换的核心任务,依赖顺序配置完全失效。

解决方案

不需要自定义任务提前拷贝类文件,直接读取所有字节码转换完成后的最终类产物,按变体维度配置JaCoCo任务即可,适配多product flavor场景:

  • 修正类文件读取路径,废弃自定义的copyClassFilesForJacoco任务,直接指向AGP输出的全量插桩后的类目录:
    • AGP 7.0及以上版本路径:${buildDir}/intermediates/asm-instrumented/classes/${变体名}/
    • AGP 4.x版本路径:${buildDir}/intermediates/transforms/HiltTransform/${变体名}/
      注意不要重复读取javac、kotlin编译任务输出的中间类目录,避免类冲突。
  • 遍历所有构建变体自动生成对应JaCoCo任务,不要硬编码单个flavor的路径,参考配置如下:
    android.applicationVariants.all { variant ->
        def variantName = variant.name.capitalize()
        def unitTestTask = tasks.getByName("test${variantName}UnitTest")
        
        tasks.register("jacoco${variantName}CoverageReport", JacocoReport) {
            dependsOn unitTestTask
            group = "verification"
            description = "Generate Jacoco coverage report for ${variant.name}"
    
            // 配置全量插桩后的最终类目录,过滤不需要统计的自动生成类
            classDirectories.setFrom(files(fileTree(
                dir: "${buildDir}/intermediates/asm-instrumented/classes/${variant.name}",
                excludes: [
                    '**/R.class',
                    '**/R$*.class',
                    '**/BuildConfig.*',
                    '**/Manifest*.*',
                    '**/hilt_aggregated_deps/**',
                    '**/*_Factory.class',
                    '**/*_MembersInjector.class',
                    '**/Hilt_*.class',
                    '**/*$Lambda$*.*'
                ]
            )))
    
            // 配置源码目录,覆盖main和对应flavor的java、kotlin路径
            sourceDirectories.setFrom(files([
                "${project.projectDir}/src/main/java",
                "${project.projectDir}/src/main/kotlin",
                "${project.projectDir}/src/${variant.flavorName}/java",
                "${project.projectDir}/src/${variant.flavorName}/kotlin"
            ]))
    
            // 配置单元测试生成的覆盖率数据路径
            executionData.setFrom(fileTree(
                dir: buildDir,
                includes: ["jacoco/${unitTestTask.name}.exec"]
            ))
    
            reports {
                xml.required.set(true)
                html.required.set(true)
            }
        }
    }
    
  • 补充避坑配置:
    • 在testOptions中排除Hilt自动生成类的覆盖率统计,避免无效数据干扰:
      testOptions {
          unitTests.all {
              jacoco {
                  excludeJdkClasses = true
                  excludes = ['**/*hilt**', '**/*Hilt*', '**/*_MembersInjector*', '**/*_Factory*']
              }
          }
      }
      
    • 如果debug构建类型开启了混淆,必须在debug混淆规则中添加-dontobfuscate和-keepattributes *,否则混淆后的类同样会和覆盖率数据不匹配。
    • 报错中提到的协程生成内部类Data$callApi$1、Hilt生成工厂类SomeFactory$SomeGenerator如果不需要纳入统计,可以直接加到classDirectories的excludes列表中过滤,不影响业务代码覆盖率准确率。
验证方式

先执行./gradlew clean清空历史构建产物,再执行对应变体的JaCoCo任务,例如flavorADebug变体对应执行./gradlew jacocoFlavorADebugCoverageReport,任务执行完成后不会再出现类不匹配的报错,覆盖率数据可正常生成。


内容的提问来源于stack exchange,提问作者NightFury

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 01:54:22