skip to content

Walk me through setting up a dedicated aggregation/collector project for a multi-module repo, including which test suite it aggregates.

level: middleimportance: should knowfreq 35%

answer

  1. thin code-free collector project
  2. apply base + jacoco-report-aggregation
  3. jacocoAggregation per module
  4. testSuiteName selects the suite
  5. report task named after the suite

basics

~10 s

Create an empty module (e.g. :coverage), apply base and jacoco-report-aggregation, add jacocoAggregation(project(...)) for each module, optionally set testSuiteName, then run testCodeCoverageReport.

solid answer

~40 s

Make a dedicated project that holds no production code — its only job is collecting coverage. In its `build.gradle.kts` apply `base` (or `java`) plus `jacoco-report-aggregation`. Add a `jacocoAggregation(project(":x"))` line for each module you want included; transitive dependencies are pulled automatically. By default it aggregates the `test` suite from `jvm-test-suite`, producing the `testCodeCoverageReport` task. If you want a different suite (say an `integrationTest` suite), configure a `JacocoCoverageReport` under the `reporting { reports { } }` block and set its `testSuiteName`, which registers a correspondingly named report task. Keeping this in a separate project avoids polluting a real module's report and gives CI a single, stable task to invoke.

code

kotlin · 19 lines
kotlin
// coverage/build.gradle.kts
plugins {
    id("base")
    id("jacoco-report-aggregation")
}

dependencies {
    jacocoAggregation(project(":app"))
    jacocoAggregation(project(":lib"))
}

reporting {
    reports {
        val testCodeCoverageReport by
            getting(JacocoCoverageReport::class) {
            testSuiteName = "test"
        }
    }
}

go deeper

for a junior

Know the basic recipe: empty module, apply the plugin, list modules, run testCodeCoverageReport.

for a middle

Explain why the collector is separate and how testSuiteName picks which suite to aggregate.

for a senior

Discuss aggregating multiple suites, CI invoking a single task, and the JacocoCoverageReport-to-task mapping.

for a principal

Standardize a convention plugin so every repo gets a consistent aggregation module and CI contract.

## Why a dedicated project You *could* apply the plugin to an existing module, but its report would then also collect *that* module — mixing 'collector' and 'collected' roles and skewing the report. The idiomatic pattern is a thin, code-free aggregation project. ## Step by step 1. **Create the module** and register it in `settings.gradle.kts`: ```kotlin // settings.gradle.kts include(":app", ":lib", ":coverage") ``` 2. **Apply the plugins** (no domain plugins): ```kotlin // coverage/build.gradle.kts plugins { id("base") id("jacoco-report-aggregation") } ``` 3. **Declare contributors**: ```kotlin dependencies { jacocoAggregation(project(":app")) jacocoAggregation(project(":lib")) } ``` 4. **(Optional) choose the suite.** The default is the `test` suite. To aggregate a custom suite you defined with `jvm-test-suite` (e.g. `integrationTest`), declare a report: ```kotlin reporting { reports { val integrationTestCodeCoverageReport by creating(JacocoCoverageReport::class) { testSuiteName = "integrationTest" } } } ``` That registers `integrationTestCodeCoverageReport`. 5. **Run it**: `./gradlew :coverage:testCodeCoverageReport`. Output lands in `coverage/build/reports/jacoco/testCodeCoverageReport/`. ## Conventions worth following - Contributing modules must apply `jacoco` (and ideally use `jvm-test-suite`) so they expose the coverage variants. - Wire CI to call the single aggregation task; don't have CI fan out across per-module reports. - The `JacocoCoverageReport` element is where `testSuiteName` lives — it's the bridge between a named test suite and a generated report task. ## Gotcha The aggregation project's own (empty) coverage is irrelevant; just don't put real classes in it, or they'll show as 0% in the merged report.

  • Do you have to list transitive dependencies in jacocoAggregation?
    No — only the entry-point modules. Transitive project dependencies that expose coverage variants are included automatically.
  • How do you aggregate an integrationTest suite instead of the unit-test suite?
    Declare a `JacocoCoverageReport` in the reporting block with `testSuiteName = "integrationTest"`; that registers an `integrationTestCodeCoverageReport` task aggregating that suite.

saying these in an interview costs you the question

  • Putting production code in the collector project (it then reports its own 0%-covered classes).
  • Forgetting that contributing modules must actually apply jacoco/jvm-test-suite to expose variants.

context