I am developing a multi-module Gradle (8.7) project, structured such that one module acts as the main application, while the others are libraries used by this application. All unit tests are located within the application module. Although these tests cover code paths in the library modules, Jacoco does not seem to generate coverage reports for these submodules, leading to inaccurately low coverage metrics. Here is the structure:
repository-root/
└── application/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ └── java/
│ │ └── test/
│ └── build.gradle.kts
├── common/
│ ├── src/
│ │ └── main/
│ │ └── java/
│ └── build.gradle.kts
└── build.gradle.kts
Issue: Main Problem: Despite having tests that cover library module code, Jacoco's reports only reflect coverage for the application module. Goal: Achieve a coverage report that accurately includes data from all modules, reflecting the true coverage achieved through tests run in the application module.
I tried to include coverage for one of the library modules (:common) by configuring the jacocoTestReport task in app/build.gradle.kts in the application module's build.gradle as follows:
tasks.jacocoTestReport {
val common = project(":common")
dependsOn(tasks.test)
dependsOn(common.tasks.test)
classDirectories.setFrom(
files("$buildDir/classes/java/main"),
files("${common.buildDir}/classes/java/main"))
sourceDirectories.setFrom(
files("${projectDir}src/main/java"),
files("${common.projectDir}/src/main/java"))
executionData.setFrom(
files("$buildDir/jacoco/test.exec"),
files("${common.buildDir}/jacoco/test.exec"))
reports {
xml.required.set(true)
csv.required.set(false)
html.required.set(true)
}
}
I want to specify that I found this solution on some forum, but no test.exec is generated in my common module. So also other solutions like generate an aggregated RootReport that I found on other question maybe won't be suitable for my case.
Answers
It seems like your configuration is on the right track, but there might be some issues with the paths or dependencies. Let's revise and adjust your configuration to ensure that Jacoco generates coverage reports for all modules correctly.
First, ensure that the jacocoTestReport
task is properly configured in the build.gradle.kts
file of your application
module:
tasks.jacocoTestReport {
dependsOn(tasks.test)
// Iterate over all subprojects to include their coverage data
project.subprojects { subproject ->
executionData.setFrom(files("${subproject.buildDir}/jacoco/test.exec"))
classDirectories.setFrom(files("${subproject.buildDir}/classes/java/main"))
sourceDirectories.setFrom(files("${subproject.projectDir}/src/main/java"))
}
reports {
xml.required.set(true)
html.required.set(true)
}
}
In this configuration:
- We use
project.subprojects
to iterate over all subprojects (modules). - For each subproject, we set the appropriate
executionData
,classDirectories
, andsourceDirectories
. - We ensure that both XML and HTML reports are generated.
Make sure that your tests are actually generating Jacoco coverage data (test.exec
files) in each submodule. If not, you may need to review your test configurations and ensure that Jacoco is properly instrumenting your code during the tests.
Additionally, ensure that all subprojects are correctly declared in the settings.gradle.kts
file in the root directory:
rootProject.name = "repository-root"
include(":application")
include(":common")
With these adjustments, Jacoco should generate accurate coverage reports for all modules in your multi-module project. If you encounter any further issues, ensure to check the Jacoco output and logs for any errors or warnings that may indicate the source of the problem.