Joining JaCoCo JUnit Reports with Gradle

In one of my previous posts, I shared my experience with generating JaCoCo reports using Gradle. One issue that I ran into is that I would get different reports for each of my test tasks. What I needed was to join the reports and get a single report for the overall test coverage of my code.

Example code

In my example code, I have a two classes, SimpleCalculator and ScientificCalculator. I have a test class for each of them, SimpleCalculatorTest and ScientificCalculatorIT. The latter is tagged as 'IT' using the JUnit 5 Tag annotation. This test acts as my integration test. You can find the full code on GitHub.

I also have JaCoCo added as a dependency in my build.gradle.

I have two different Gradle tasks: unitTests and integrationTests that I use to run the unit and integration tests separately.

plugins {
    id 'java'
    id 'jacoco'
}

group 'co.igorski'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
}

task unitTests(type: Test) {
    useJUnitPlatform {
        excludeTags 'IT'
    }
    testLogging {
        events "passed", "skipped", "failed"
    }
}

task integrationTests(type: Test) {
    useJUnitPlatform {
        includeTags 'IT'
    }
    testLogging {
        events "passed", "skipped", "failed"
    }
}

When I run these tasks, two things happen. First, JaCoCo generates two separate reports in binary format: unitTests.exec and integrationTests.exec. Second, Gradle generates HTML reports for each of the tasks. Don't let these tests confuse you. These are not coverage reports but simple test execution reports which Gradle generates by default. If you want, you can disable them by adding this line to your test tasks:

reports.html.enabled = false

Join all generated reports

I found many discussions and different approaches on how to do this. It is especially tricky to do this for Gradle projects with multiple subprojects. But most of these approaches try to join the JaCoCo binary (.exec) reports into one report again in the binary format. But why in the binary format again? My guess is because of SonarQube that would use this report when doing the scans. However, SonarQube will no longer support the binary format, so there is no point in joining the reports in a binary format. Instead, I joined them in a single xml report.

After some tinkering, the code for the Gradle task turned out to be extremely simple:

task combineJaCoCoReports(type: JacocoReport) {
    executionData fileTree(project.buildDir.absolutePath).include("jacoco/*.exec")
    classDirectories.setFrom files(project.sourceSets.main.output)
    sourceDirectories.setFrom files(project.sourceSets.main.allSource.srcDirs)

    reports {
        xml.enabled(true)
        html.enabled(true)
    }
}

This task will look into into the build/jacoco/ folder for binary (.exec) reports generated by a previous task and merge them all. In my case, the reports were placed in build/reports/jacoco/combineJaCoCoReports, where build is my build folder.

Sources