【问题标题】:Jacoco and Unit Tests Code Coverage with android-gradle-plugin >= 1.1Jacoco 和 android-gradle-plugin >= 1.1 的单元测试代码覆盖率
【发布时间】:2015-02-18 15:10:12
【问题描述】:

我最近开始在我的一个项目中集成android-gradle-plugin 1.1.0。该项目使用robolectric 2.4 运行单元测试。

这是一个多模块项目,具有非常复杂的依赖关系(一些模块依赖于其他模块)。类似的东西:

--> application-module (dependsOn: module1, module2, module-core)
    --> module1 (dependsOn: module-core)
    --> module2 (dependsOn: module-core)
    --> module-core (dependsOn: module3, module4)
        --> module3 (library dependencies)
        --> module4 (library dependencies)

更清晰的图片请查看jacoco-example项目。

我尝试集成 JaCoCo 以生成单元测试报告,但在我看来,它只运行 androidTests,这基本上是仪器测试。

经过一些谷歌搜索后,我在 GitHub 和其他文章上发现了一些项目,但它们主要集中在 android-gradle-plugin 的早期版本或使用其他第三方插件,如 android-unit-test for example here

可能是我失去了使用 Google 搜索的能力。但是有人可以指出我可以找到一些关于 android gradle 插件中的新内容以及如何仅针对单元测试运行 jacoco 任务的文档的方向吗?

更新

采用nenick's example的脚本:

apply plugin: "jacoco"

configurations {
    jacocoReport
}

task jacocoReport(dependsOn: 'testDebug') << {
    ant {
        taskdef(name:'jacocoreport',
                classname: 'org.jacoco.ant.ReportTask',
                classpath: configurations.jacocoReport.asPath)

        mkdir dir: "${buildDir}/test-coverage-report"
        mkdir dir: "${buildDir}/reports/jacoco/test/"

        jacocoreport {
            executiondata = files("${buildDir}/jacoco/testDebug.exec")

            structure(name: "${rootProject.name}") {
                classfiles {
                    fileset (dir: "${buildDir}/intermediates/classes/debug") {
                        //exclude(name: '**/*_*.class')
                        exclude(name: '**/R.class')
                        exclude(name: '**/R$*.class')
                        exclude(name: '**/BuildConfig.class')
                    }
                }

                sourcefiles {
                    fileset dir: "src/main/java"
                    fileset dir: "${buildDir}/generated/source/buildConfig/debug"
                    fileset dir: "${buildDir}/generated/source/r/debug"
                }
            }

            xml destfile: "${buildDir}/reports/jacoco/test/jacocoTestReport.xml"
            html destdir: "${buildDir}/test-coverage-report/"
        }
    }
}

dependencies {
    jacocoReport 'org.jacoco:org.jacoco.ant:0.7.2.201409121644'
}

之后,./gradlew jacocoReport 执行并生成报告,但它显示 0(零)测试覆盖率,这是不可能的,因为至少有一半的类都经过测试。

UPDATE_2

试用了这个example。将下一个任务添加到我的一个 gradle 构建文件中:

task jacocoTestReport(type:JacocoReport, dependsOn: "testDebug") {
    group = "Reporting"
    description = "Generate Jacoco coverage reports"

    classDirectories = fileTree(
            dir: "${buildDir}/intermediates/classes/debug",
            excludes: ['**/R.class',
                       '**/R$*.class',
                       '**/*$ViewInjector*.*',
                       '**/BuildConfig.*',
                       '**/Manifest*.*']
    )

    sourceDirectories = files("${buildDir.parent}/src/main/java")
    additionalSourceDirs = files([
            "${buildDir}/generated/source/buildConfig/debug",
            "${buildDir}/generated/source/r/debug"
    ])
    executionData = files("${buildDir}/jacoco/testDebug.exec")

    reports {
        xml.enabled = true
        html.enabled = true
    }
}

同样的问题,生成了报告,但代码覆盖率仍然为零。

UPDATE_3

UPDATE_2 中的任务似乎有效,但仅适用于具有apply plugin: 'com.android.application' 的模块(报告正确生成)。但是对于 android 库 (apply plugin: 'com.android.library') 的模块,报告显示零覆盖率,尽管模块包含比应用程序模块更多的测试。

UPDATE_4

创建了一个简单的示例项目来演示我的问题。目前,如果您运行./gradlew jacocoReport,则会生成报告,但不会显示模块项目的测试覆盖率。看到这个link

简短说明:当测试是 AndroidUnitTests(白化 JUnit 4 和 Robolectric)时,JaCoCo 报告显示所有模块的覆盖率。

有什么想法吗?

【问题讨论】:

  • 我在 1.1-rc1 中有类似的工作(不可靠)。现在我正在运行 1.1 final 并且只会出错。期待其他回复。
  • 您似乎正在尝试为多项目设置生成覆盖范围?我之前也遇到过类似的问题,详细的解决方法在我的博客里:hidroh.github.io/2014/05/15/…
  • UPDATE_2 在 gradle 1.1.3 的库项目中为我工作
  • 那么您是否能够获得代码覆盖率统计信息?我只能获取“androidTest”的代码覆盖率统计信息,但我想分析“test”。

标签: android unit-testing android-gradle-plugin jacoco


【解决方案1】:

麻烦之后,我决定为此创建一个open source Gradle plugin

根 build.gradle

buildscript {
    repositories {
        mavenCentral() // optional if you have this one already
    }
    dependencies {
        classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.16.0'
    }
}

apply plugin: 'com.vanniktech.android.junit.jacoco'

然后直接执行

./gradlew jacocoTestReportDebug

它将在调试模式下运行 JUnit 测试,然后在相应的构建目录中以 XML 和 HTML 形式为您提供 Jacoco 输出。

它还支持风味。将创建红色和蓝色 2 种口味的任务

  • jacocoTestReportRedDebug
  • jacocoTestReportBlueDebug
  • jacocoTestReportRedRelease
  • jacocoTestReportBlueRelease

【讨论】:

  • 非常感谢 - 但是当我尝试使用上述插件时,我收到以下错误:java.lang.UnsupportedClassVersionError: com/vanniktech/android/junit/jacoco/Generation : Unsupported major。次要版本 52.0 有什么想法吗?
  • 看起来我需要 Java 8 才能让它工作 - 我现在有一份工作报告。谢谢!!
  • @danwilkie 是的,您需要 Java 8,因为我使用 Java 8 编译了该版本。如果这对您有帮助,请随时投票。
  • 它完美无瑕,在我的项目中,我在存储库中有 jcenter() 并且仍然有效(以防万一有人想知道)。干得好。
  • @danwilkie 使用最新版本,您不再需要 Java 8。 Java 7 就够了
【解决方案2】:

经过一些额外的搜索,我偶然发现了这个project 我必须进行一些修改,以便解决方案适用于我的项目类型,但现在测试覆盖率报告已正确生成。

我已将采用的更改推送到我的example github repo,以防将来有人遇到类似问题。

【讨论】:

  • 您能否向我们解释一下这些修改是什么以及您偶然发现了什么!
  • 我会投票赞成将正确答案更改为 Niklas 提供的答案,因为它是一种易于实施的解决方案。
【解决方案3】:

警告:这是一个 hack!使用上面的配置,我整理了一个技巧,根据选择的构建任务在应用程序和库之间切换 android 插件。这对我很有效,因为我最终不会在设置应用程序模式的情况下提交代码。

// dynamically change the android plugin to application if we are running unit tests or test reports.
project.ext.androidPlugin = 'com.android.library'
for (String taskName : project.gradle.startParameter.taskNames) {
    if (taskName.contains('UnitTest') || taskName.contains('jacocoTestReport')) {
        project.ext.androidPlugin = 'com.android.application'
        break
    }
}

logger.lifecycle("Setting android pluging to ${project.ext.androidPlugin}")
apply plugin: project.ext.androidPlugin

...

apply plugin: 'jacoco'

configurations {
    jacocoReport
}

task jacocoTestReport(type:JacocoReport, dependsOn: "testDebug") {
    group = "Reporting"
    description = "Generate Jacoco coverage reports"

    classDirectories = fileTree(
            dir: "${buildDir}/intermediates/classes/debug",
            excludes: ['**/R.class',
                       '**/R$*.class',
                       '**/*$ViewInjector*.*',
                       '**/BuildConfig.*',
                       '**/Manifest*.*']
    )

    sourceDirectories = files("${buildDir.parent}/src/main/java")
    additionalSourceDirs = files([
            "${buildDir}/generated/source/buildConfig/debug",
            "${buildDir}/generated/source/r/debug"
    ])
    executionData = files("${buildDir}/jacoco/testDebug.exec")

    reports {
        xml.enabled = true
        html.enabled = true
    }
}

让我们希望 android 工具团队尽快解决这个问题。

【讨论】:

  • 不幸的是,对我来说它不起作用,因为我的一些模块依赖于其他模块。当尝试为应用程序模块运行单元测试时,这将不起作用。感谢您的回复。
【解决方案4】:

我使用blog post 为 gradle 1.2 设置了单元测试。然后我将在这里和其他地方找到的信息拼凑在一起,以将代码覆盖率添加到独立模块而不是整个项目。在我的库模块build.gradle 文件中,我添加了以下内容:

apply plugin: 'jacoco'

def jacocoExcludes = [
        'com/mylibrary/excludedpackage/**'
]

android {
    ...
}

android.libraryVariants.all { variant ->
    task("test${variant.name.capitalize()}WithCoverage", type: JacocoReport, dependsOn: "test${variant.name.capitalize()}") {
        group = 'verification'
        description = "Run unit test for the ${variant.name} build with Jacoco code coverage reports."

        classDirectories = fileTree(
                dir: variant.javaCompile.destinationDir,
                excludes: rootProject.ext.jacocoExcludes.plus(jacocoExcludes)
        )
        sourceDirectories = files(variant.javaCompile.source)
        executionData = files("${buildDir}/jacoco/test${variant.name.capitalize()}.exec")

        reports {
            xml.enabled true
            xml.destination "${buildDir}/reports/jacoco/${variant.name}/${variant.name}.xml"
            html.destination "${buildDir}/reports/jacoco/${variant.name}/html"
        }
    }
}

在我的项目build.gradle 文件中,我添加了常见的排除:

ext.jacocoExcludes = [
    'android/**',
    '**/*$$*',
    '**/R.class',
    '**/R$*.class',
    '**/BuildConfig.*',
    '**/Manifest*.*',
    '**/*Service.*'
]

此外,看起来单元测试的代码覆盖率可能会在未来内置Issue 144664

【讨论】:

    【解决方案5】:

    我终于能够使用 Android Studio 1.1 查看 JUnit 测试的代码覆盖率。

    jacoco.gradle

    apply plugin: 'jacoco'
    
    jacoco {
        toolVersion "0.7.1.201405082137"
    }
    
    def coverageSourceDirs = [
            "$projectDir/src/main/java",
    ]
    
    task jacocoTestReport(type: JacocoReport, dependsOn: "testDebug") {
        group = "Reporting"
        description = "Generate Jacoco coverage reports after running tests."
        reports {
            xml.enabled = true
            html.enabled = true
        }
        classDirectories = fileTree(
                dir: './build/intermediates/classes/debug',
                excludes: ['**/R*.class',
                           '**/*$InjectAdapter.class',
                           '**/*$ModuleAdapter.class',
                           '**/*$ViewInjector*.class'
                ]
        )
        sourceDirectories = files(coverageSourceDirs)
        executionData = files("$buildDir/jacoco/testDebug.exec")
        // Bit hacky but fixes https://code.google.com/p/android/issues/detail?id=69174.
        // We iterate through the compiled .class tree and rename $$ to $.
        doFirst {
            new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
                if (file.name.contains('$$')) {
                    file.renameTo(file.path.replace('$$', '$'))
                }
            }
        }
    }
    

    然后在模块的 build.gradle 文件中(我把它放在androiddependencies 之间):

    apply from: '../jacoco.gradle'
    

    也在androiddefaultConfig 块中。我已经添加了这个(不知道是否有必要,但我从this blog得到这个):

    android {
        defaultConfig {
            testHandleProfiling true
            testFunctionalTest true
        }
    }
    

    享受吧。

    【讨论】:

    • 这为我生成了一份报告......但它只显示了我有多少测试以及它们是否失败。有什么方法可以获得代码覆盖率结果?
    【解决方案6】:

    你可以尝试使用这个 Gradle 插件: https://github.com/arturdm/jacoco-android-gradle-plugin

    基本上,您只需像这样应用它:

    buildscript {
      repositories {
        jcenter()
      }
      dependencies {
        classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1'
      }
    }
    
    apply plugin: 'com.android.library' // or 'com.android.application'
    apply plugin: 'jacoco-android'
    

    因此,您应该为每个变体获得一个JacocoReport 任务。运行以下命令为所有这些生成代码覆盖率报告。

    $ ./gradlew jacocoTestReport
    

    【讨论】:

    • 喜欢这个主意。是否将报告汇总为一个?
    • 您会获得每个变体的单独报告。这与仪器测试的行为相同。这些变体的输出目录是build/reports/jacoco/jacoco${testTaskNameOfVariant}Report
    • @behelit,您可以查看这篇文章allegro.tech/2016/03/…,了解有关这些报告的更多信息,如果您在某些时候仍然遇到困难,提供更多详细信息会有所帮助。
    【解决方案7】:

    我解决了 JaCoCo 的问题并使其与最新的 gradle android 插件 1.1.3 一起使用

    带有最新 gradle 脚本的项目:https://github.com/OleksandrKucherenko/meter

    参考资料:

    如何在 Android Studio 单元测试中附加自己的实现而不是 Mocks? https://plus.google.com/117981280628062796190/posts/8jWV22mnqUB

    给所有尝试在 android 构建中使用 JaCoCo 覆盖的小提示...意外发现!!! https://plus.google.com/117981280628062796190/posts/RreU44qmeuP

    JaCoCo XML/HTML 单元测试报告 https://plus.google.com/u/0/+OleksandrKucherenko/posts/6vNWkkLed3b

    【讨论】:

    • btw JaCoCo、PMD、Findbugs、Checkstyle 也在项目中工作。
    • 这是否需要 1.1.3?还是只是您使用的是 1.1.3?
    【解决方案8】:

    我遇到了和你一样的问题。今天我确实完全删除了android studio、android sdk、gradle。然后重新安装一切。之后,我只是在应用内添加了 build.gradle。

    调试{ testCoverageEnabled 真 } 然后我运行 ./gradlew connectedChec。一切正常。 Android Studio 默认 Jacoco 对我来说工作正常。我认为也可以创建一个 jacocoTestReport 任务然后创建代码覆盖率。我不知道为什么 gradle 和 android studio 以前不起作用。

    【讨论】:

      【解决方案9】:

      请创建一个示例,我可以看看。我猜是缺少路径配置。

      • 包括所有覆盖文件 (*.exec)
      • 添加所有源路径 (module/src/main/java)
      • 添加所有类路径(module/build/intermediates/classes/debug)

      这里有两个例子

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-28
        • 2016-05-22
        • 1970-01-01
        • 2014-05-11
        • 1970-01-01
        • 2016-04-07
        相关资源
        最近更新 更多