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?
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
testtask. Instead, there are multiple test tasks, one for each build variant. The test tasks are namedtest<Variant>UnitTest. For example, you might havetestDebugUnitTestandtestReleaseUnitTest.When you try to access
tasks.testin an Android project, it results in an "Unresolved reference: test" error because there is no task with the nametest.Similarly,
finalizedBy(tasks.jacocoTestReport)is a way to specify task dependencies, saying that thejacocoTestReporttask should always be run after thetesttask. However, since there is notesttask 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.ktsthat automatically applies the correct Jacoco setup for each module that uses the Android plugin.Using a Gradle DLS script:
That script will check every subproject in the build. If a subproject applies your IDs, like
Dependencies.Plugins.androidApplicationorDependencies.Plugins.androidLibraryplugin, it adds Jacoco tasks for that subproject.For each of these projects, add a
jacocoTestReporttask which generates the Jacoco reports.One important thing to note is that the Android Gradle plugin creates a
testtask for each build variant of the app/library module. For example, if you havedebugandreleasebuild types, the Android Gradle plugin will createtestDebugUnitTestandtestReleaseUnitTesttasks.Meaning, you will need to run the specific
testtask and thenjacocoTestReportlike this:That will run the unit tests for
module1and then generate the Jacoco report. Note that if you are using product flavors, you might have more than two types oftesttasks (e.g.,testFreeDebugUnitTest,testPaidDebugUnitTest, etc.), so do adjust your command accordingly to fit your project configuration.Consolidating JaCoCo coverage reports from multiple modules into a single report in a multi-module Android project means aggregating the execution data (
*.execfiles) and source information from all relevant subprojects.That would require a custom Gradle task in your root
build.gradle.ktsfile that gathers data from all modules and generates a comprehensive report.In your root
build.gradle.kts, define a task that will collect and aggregate coverage data from all subprojects. That task will depend on all individualjacocoTestReporttasks to make sure all coverage data is available before aggregation.The example paths provided (
src/main/kotlin,intermediates/javac/debug/classes,jacoco/testDebugUnitTest.exec) are based on a typical Android project structure: adapt them based on your project's configuration:src/main/javaif your project primarily uses Java.debugwithreleaseif you are generating reports for release builds).To generate the aggregated report, run the following command:
That would first cleans previous builds to make sure fresh data is used for the report, then runs the
jacocoRootReporttask, which aggregates data from all subprojects.