Setting up jacoco for multi-module Android codebase with Gadle kotlin

huangapple go评论74阅读模式
英文:

Setting up jacoco for multi-module Android codebase with Gadle kotlin

问题

我目前正在为我团队构建的一个Android代码库设置Jacoco。我在Android方面没有太多经验,但之前我在一个Spring Boot代码库中设置过Jacoco,以便我可以在Sonarqube中跟踪测试覆盖率。但是在Android代码库中我遇到了一些困难。

所以目录结构看起来像这样:

MyApp/
├─ app/
│  ├─ build/
│  ├─ src/
│  ├─ build.gradle.kts
├─ buildSrc/
│  ├─ build.gradle.kts
├─ modules/
│  ├─ module1/
│  │  ├─ src/
│  │  ├─ build.gradle.kts
│  ├─ module2/
│  │  ├─ src/
│  │  ├─ build.gradle.kts
├─ build.gradle.kts
├─ gradle.properties
├─ gradlew
├─ settings.gradle.kts

我尝试在MyApp/build.gradle.kts中添加jacoco

plugins {
    id(Dependencies.Plugins.androidApplication) version Dependencies.Versions.androidAppplication apply false
    id(Dependencies.Plugins.androidLibrary) version Dependencies.Versions.androidLibrary apply false
    id(Dependencies.Plugins.hilt) version Dependencies.Versions.hilt apply false
    id(Dependencies.Plugins.openApi) version Dependencies.Versions.openApi
    id(Dependencies.Plugins.kotlinAndroid) version Dependencies.Versions.kotlinAndroid apply false
    id(Dependencies.Plugins.sonarqube) version Dependencies.Versions.sonarqube
    
    id("jacoco")
}

我尝试执行bash gradlew test jacocoTestReport,但是它显示:

Task 'jacocoTestReport' not found in root project 'MyApp' and its subprojects.

根据JaCoco插件文档(https://docs.gradle.org/current/userguide/jacoco_plugin.html),我尝试在MyApp/build.gradle.kts中添加以下行:

tasks.test {
    finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run
}
tasks.jacocoTestReport {
    dependsOn(tasks.test) // tests are required to run before generating the report
}

但是输出显示类似以下内容:

Script compilation errors:

  Line 12: tasks.test {
                 ^ Unresolved reference: test

  Line 13:     finalizedBy(tasks.jacocoTestReport)                                              
               ^ Unresolved reference: finalizedBy

我之前在一个Spring Boot项目中进行了类似的配置,它可以正常运行。但是在这里它甚至无法检测到任务jacocoTestReport

我做错了什么?

英文:

I am currently setting up Jacoco in an Android codebase that my team built. I don't have much experience in Android but I have set up Jacoco before in a Spring Boot codebase so that I can track the test coverage in Sonarqube. But I am having hard time doing in the Android codebase.

So the directory layout looks like this

MyApp/
├─ app/
│  ├─ build/
│  ├─ src/
│  ├─ build.gradle.kts
├─ buildSrc/
│  ├─ build.gradle.kts
├─ modules/
│  ├─ module1/
│  │  ├─ src/
│  │  ├─ build.gradle.kts
│  ├─ module2/
│  │  ├─ src/
│  │  ├─ build.gradle.kts
├─ build.gradle.kts
├─ gradle.properties
├─ gradlew
├─ settings.gradle.kts

I tried adding jacoco in the MyApp/build.gradle.kts.

plugins {
    id(Dependencies.Plugins.androidApplication) version Dependencies.Versions.androidAppplication apply false
    id(Dependencies.Plugins.androidLibrary) version Dependencies.Versions.androidLibrary apply false
    id(Dependencies.Plugins.hilt) version Dependencies.Versions.hilt apply false
    id(Dependencies.Plugins.openApi) version Dependencies.Versions.openApi
    id(Dependencies.Plugins.kotlinAndroid) version Dependencies.Versions.kotlinAndroid apply false
    id(Dependencies.Plugins.sonarqube) version Dependencies.Versions.sonarqube
    
    id("jacoco")
}

I tried executing bash gradlew test jacocoTestReport but it says

Task 'jacocoTestReport' not found in root project 'MyApp' and its subprojects.

I tried adding the line below in the MyApp/build.gradle.kts, per JaCoco Plugin documentation (https://docs.gradle.org/current/userguide/jacoco_plugin.html)

tasks.test {
    finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run
}
tasks.jacocoTestReport {
    dependsOn(tasks.test) // tests are required to run before generating the report
}

but the output says something like below

Script compilation errors:

  Line 12: tasks.test {
                 ^ Unresolved reference: test

  Line 13:     finalizedBy(tasks.jacocoTestReport)                                              
               ^ Unresolved reference: finalizedBy

I did similar configuration in a Spring Boot project before which ran normal. But here it cannot even detect task jacocoTestReport.

What did I do wrong?

答案1

得分: 2

在传统的Java或Kotlin JVM项目中,有一个名为tasks.test的单个测试任务。

然而,在Android项目中,没有一个单独的test任务。相反,有多个测试任务,每个构建变体都有一个。这些测试任务的名称为test<Variant>UnitTest。例如,你可能会有testDebugUnitTesttestReleaseUnitTest

当你尝试在Android项目中访问tasks.test时,会出现"Unresolved reference: test"错误,因为没有名为test的任务。

类似地,finalizedBy(tasks.jacocoTestReport)是一种指定任务依赖关系的方式,表示jacocoTestReport任务应该在test任务之后始终运行。然而,由于Android项目中没有test任务,这会导致"Unresolved reference: finalizedBy"错误。


根据"How do I add plugins to subprojects based on what plugins are present?",你可能需要在根目录的build.gradle.kts中创建一个Jacoco设置,自动为使用Android插件的每个模块应用正确的Jacoco设置。

使用Gradle DLS脚本:

import org.gradle.testing.jacoco.tasks.JacocoReport

plugins {
    // your existing plugins...

    id("jacoco")
}

// rest of your configurations...

subprojects { subproject ->
    subproject.plugins.withId(Dependencies.Plugins.androidApplication) {
        setupJacoco(subproject)
    }
    subproject.plugins.withId(Dependencies.Plugins.androidLibrary) {
        setupJacoco(subproject)
    }
}


fun setupJacoco(project: Project) {
    project.tasks.register("jacocoTestReport", JacocoReport::class.java) { task ->
        task.group = "Reporting"
        task.description = "Generate Jacoco coverage reports after running tests."
        task.reports {
            xml.isEnabled = true
            html.isEnabled = true
        }
        task.executionData.setFrom(
            project.fileTree(project.buildDir).apply {
                include("**/jacoco/*.exec")
            }
        )
        task.sourceDirectories.setFrom(
            project.files(
                project.extensions.getByType<SourceSetContainer>().getByName("main").allSource.srcDirs
            )
        )
        task.classDirectories.setFrom(
            project.fileTree(
                project.extensions.getByType<SourceSetContainer>().getByName("main").output.classesDirs
            )
        )
    }
}

该脚本将检查构建中的每个子项目。如果子项目应用了你的IDs,比如Dependencies.Plugins.androidApplicationDependencies.Plugins.androidLibrary插件,它会为该子项目添加Jacoco任务。对于每个项目,添加一个生成Jacoco报告的jacocoTestReport任务。

需要注意的一点是,Android Gradle插件会为每个应用程序/库模块的每个构建变体创建一个test任务。例如,如果你有debugrelease构建类型,Android Gradle插件将创建testDebugUnitTesttestReleaseUnitTest任务。

也就是说,你需要运行特定的test任务,然后再运行jacocoTestReport,如下所示:

# 在修改build.gradle.kts后执行一次
./gradlew --refresh-dependencies

# 每次生成报告时执行以下命令
./gradlew module1:testDebugUnitTest module1:jacocoTestReport

这将运行module1的单元测试,然后生成Jacoco报告。请注意,如果你使用产品风味,可能会有多种类型的test任务(例如testFreeDebugUnitTesttestPaidDebugUnitTest等),所以根据你的项目配置相应调整命令。

英文:

In a traditional Java or Kotlin JVM project, there is a single test task, accessible via tasks.test.

However, in an Android project, there isn't a single test task. Instead, there are multiple test tasks, one for each build variant. The test tasks are named test&lt;Variant&gt;UnitTest. For example, you might have testDebugUnitTest and testReleaseUnitTest.

When you try to access tasks.test in an Android project, it results in an "Unresolved reference: test" error because there is no task with the name test.

Similarly, finalizedBy(tasks.jacocoTestReport) is a way to specify task dependencies, saying that the jacocoTestReport task should always be run after the test task. However, since there is no test task in an Android project, this results in an "Unresolved reference: finalizedBy" error.


Following "How do I add plugins to subprojects based on what plugins are present?", you might need to create a Jacoco setup in your root build.gradle.kts that automatically applies the correct Jacoco setup for each module that uses the Android plugin.

Using a Gradle DLS script:

import org.gradle.testing.jacoco.tasks.JacocoReport

plugins {
    // your existing plugins...

    id(&quot;jacoco&quot;)
}

// rest of your configurations...

subprojects { subproject -&gt;
    subproject.plugins.withId(Dependencies.Plugins.androidApplication) {
        setupJacoco(subproject)
    }
    subproject.plugins.withId(Dependencies.Plugins.androidLibrary) {
        setupJacoco(subproject)
    }
}


fun setupJacoco(project: Project) {
    project.tasks.register(&quot;jacocoTestReport&quot;, JacocoReport::class.java) { task -&gt;
        task.group = &quot;Reporting&quot;
        task.description = &quot;Generate Jacoco coverage reports after running tests.&quot;
        task.reports {
            xml.isEnabled = true
            html.isEnabled = true
        }
        task.executionData.setFrom(
            project.fileTree(project.buildDir).apply {
                include(&quot;**/jacoco/*.exec&quot;)
            }
        )
        task.sourceDirectories.setFrom(
            project.files(
                project.extensions.getByType&lt;SourceSetContainer&gt;().getByName(&quot;main&quot;).allSource.srcDirs
            )
        )
        task.classDirectories.setFrom(
            project.fileTree(
                project.extensions.getByType&lt;SourceSetContainer&gt;().getByName(&quot;main&quot;).output.classesDirs
            )
        )
    }
}

That script will check every subproject in the build. If a subproject applies your IDs, like Dependencies.Plugins.androidApplication or Dependencies.Plugins.androidLibrary plugin, it adds Jacoco tasks for that subproject.
For each of these projects, add a jacocoTestReport task which generates the Jacoco reports.

One important thing to note is that the Android Gradle plugin creates a test task for each build variant of the app/library module. For example, if you have debug and release build types, the Android Gradle plugin will create testDebugUnitTest and testReleaseUnitTest tasks.

Meaning, you will need to run the specific test task and then jacocoTestReport like this:

# Do this once, after making changes to your build.gradle.kts 
./gradlew --refresh-dependencies

# Then, each time you want to generate the report
./gradlew module1:testDebugUnitTest module1:jacocoTestReport

That will run the unit tests for module1 and then generate the Jacoco report. Note that if you are using product flavors, you might have more than two types of test tasks (e.g., testFreeDebugUnitTest, testPaidDebugUnitTest, etc.), so do adjust your command accordingly to fit your project configuration.

huangapple
  • 本文由 发表于 2023年7月27日 15:08:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/76777264.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定