skip to content

How would you architect cross-project reporting so that unit and integration test suites each get their own aggregated report, and how does this relate to coverage aggregation?

level: principalimportance: nice to knowfreq 18%

answer

  1. jvm-test-suite = first-class suites
  2. one AggregateTestReport per suite name
  3. jacoco-report-aggregation = coverage twin
  4. convention plugin to standardize
  5. all variant/attribute driven

basics

~10 s

Model each suite with jvm-test-suite, then register one AggregateTestReport per suite name (e.g. test and integrationTest). For coverage, apply the sibling jacoco-report-aggregation plugin, which works the same way for JaCoCo data.

solid answer

~40 s

Use `jvm-test-suite` so each suite (`test`, `integrationTest`, etc.) is a first-class entity with its own result variant. Because `test-report-aggregation` keys aggregation off `testSuiteName`, you register a separate `AggregateTestReport` per suite — one combined unit-test report and one combined integration-test report — giving teams clean, separated dashboards. For coverage, the parallel **`jacoco-report-aggregation`** plugin uses the identical variant-aware model to merge JaCoCo execution data from many projects into one coverage report, also keyed by suite. Put all this in a **convention plugin** so every repo wires the same aggregating project, suite names, and CI artifact paths. The result is a standardized, cache-friendly reporting layer: declare project dependencies once, get per-suite test reports plus a unified coverage report, all resolved through attributes rather than brittle paths.

code

kotlin · 10 lines
kotlin
reporting {
    reports {
        val testAggregateTestReport by getting(AggregateTestReport::class) {
            testSuiteName = "test"
        }
        val integrationTestAggregateTestReport by creating(AggregateTestReport::class) {
            testSuiteName = "integrationTest"
        }
    }
}

go deeper

for a junior

Out of scope; just know multiple suites can each have their own report.

for a middle

Recall that you register one AggregateTestReport per suite name and that JaCoCo has a parallel aggregation plugin.

for a senior

Wire per-suite aggregation with jvm-test-suite and pair it with jacoco-report-aggregation.

for a principal

Codify the whole reporting layer in a convention plugin as the single governance point, ensuring uniform suites, reports, and CI artifacts across the org.

## First-class suites with jvm-test-suite The **`jvm-test-suite`** plugin models test suites declaratively. The built-in `test` suite is unit tests; you add others: ```kotlin testing { suites { val test by getting(JvmTestSuite::class) val integrationTest by registering(JvmTestSuite::class) { useJUnitJupiter() dependencies { implementation(project()) } } } } ``` Each suite produces its **own** result data exposed via its **own** outgoing variant, tagged with the suite name. ## One aggregate report per suite Because `test-report-aggregation` binds a report to a `testSuiteName`, you register one aggregate per suite on the aggregating project: ```kotlin plugins { id("test-report-aggregation") } dependencies { testReportAggregation(project(":service-a")) testReportAggregation(project(":service-b")) } reporting { reports { val testAggregateTestReport by getting(AggregateTestReport::class) { testSuiteName = "test" } val integrationTestAggregateTestReport by creating(AggregateTestReport::class) { testSuiteName = "integrationTest" } } } ``` Now `testAggregateTestReport` merges unit results and `integrationTestAggregateTestReport` merges integration results — two clean, separate dashboards. ## Coverage: the sibling plugin **`jacoco-report-aggregation`** is the coverage twin. It applies the same attribute-driven resolution to collect JaCoCo `.exec` execution data across projects and render one combined coverage report, again keyed by suite via a `JacocoCoverageReport`. You declare `jacocoAggregation(project(...))` dependencies analogously. ## Standardize with a convention plugin Duplicating this wiring across dozens of repos invites drift. Extract a **convention plugin** (in `buildSrc` or an included build) that: - Applies `jvm-test-suite`, `test-report-aggregation`, and `jacoco-report-aggregation`. - Defines the standard suite names. - Registers the per-suite aggregate reports. - Documents the canonical artifact paths for CI. Teams then opt in by applying one plugin id, and every repo reports identically. ## Why this architecture - **Separation**: unit vs integration dashboards never blur together. - **Uniformity**: one coverage report, one report per suite, everywhere. - **Cache/correctness**: all inputs flow through variant resolution, so build cache and up-to-date checks apply. - **Governance**: the convention plugin is the single control point for reporting policy.

  • What plugin gives you the coverage equivalent of test-report-aggregation?
    `jacoco-report-aggregation`, which merges JaCoCo execution data across projects into one coverage report using the same variant-aware resolution and suite keying.
  • Why register a separate AggregateTestReport per suite instead of one for everything?
    Reports are bound to a `testSuiteName`, and separating unit from integration gives cleaner, independently-interpretable dashboards; one report can't mix suites meaningfully.
  • How do you stop this wiring from drifting across many repos?
    Encapsulate it in a convention plugin (buildSrc or included build) so every repo applies one id and inherits identical suites, reports, and artifact paths.

saying these in an interview costs you the question

  • Thinking a single aggregate report can cleanly merge unrelated suites like unit and integration tests.
  • Believing coverage aggregation needs a totally different mechanism — it's the same variant model via jacoco-report-aggregation.
  • Hand-duplicating aggregation config in every project instead of a convention plugin.

context