Skip to content

A couple Gradle plugins to make Android modules to work with JaCoCo Report Aggregation Plugin and Test Report Aggregation Plugin

License

Notifications You must be signed in to change notification settings

gmazzo/gradle-android-test-aggregation-plugin

Repository files navigation

GitHub Gradle Plugin Portal Build Status Coverage Users

gradle-android-test-aggregation-plugin

A couple of Gradle plugins to make Android modules work with JaCoCo Report Aggregation Plugin and Test Report Aggregation Plugin

Note

Since the arrival of Kover 0.8.0-Beta2, this project loses momentum, as test aggregation of JVM/Kotlin/Android mono-repos is supported out of the box, providing a much better coverage experience than JaCoCo

plugins {
    id("org.jetbrains.kotlinx.kover") version "0.8.0-Beta2"
}

subprojects { apply(plugin = "org.jetbrains.kotlinx.kover") }
kover.merge { allProjects() }

Usage

Apply the plugin at the root project:

plugins {
    id("io.github.gmazzo.test.aggregation.coverage") version "<latest>" 
    // and/or
    id("io.github.gmazzo.test.aggregation.results") version "<latest>"
}

Note

This plugin can not be applied along with the java one because it conflicts. If you have a Java root project, it's recommended to move it to a dedicated module

The jacocoAggregatedReport (for coverage) and testAggregatedReport (for results) will be created to aggregate test results from all projects in the build

The following is the old README.me of the demo project of my Medium article about this topic, now promoted to dedicated Gradle plugins: io.github.gmazzo.test.aggregation.coverage and io.github.gmazzo.test.aggregation.results

Filtering content

The plugins will automatically aggregate android modules and java modules that also apply jacoco plugin on the jacocoAggregation and the testReportAggregation configurations.

You control which projects are effectively included by using the DSL:

testAggregation {
  modules {
    include(project(":app"))
    exclude(projects.lib) // typesafe accessors are also supported!
  }
}

Filtering coverage classes

You can use the DSL to include/exclude .class files from the aggregated JaCoCo coverage report:

testAggregation {
    coverage {
        include("com/**/Login*") // will only include classes starting with `com.` containing `Login` on its name
    }
}

It's important to realize the filtering is done at .class file level (compiled classes). You should not use classes names here but GLOB patterns.

Demo project for aggregating Jacoco Android & JVM coverage reports

This is an example project that illustrates how can the JaCoCo Report Aggregation Plugin and Test Report Aggregation Plugin can be used to aggregate a complex Android project with JVM modules in a single :jacocoAggregatedReport and :testAggregatedReport tasks.

Project structure

  • A plugin included build that provides the coverage root plugin
  • A demo-project with:
    • An app android module (with Robolectric tests)
    • A login android library module (with JUnit4/JVM tests)
    • A domain jvm module (with tests)

The test-aggregation root plugin

The plugin fills the gaps between AGP and JaCoCo Report Aggregation Plugin by providing the necessary setup missing:

  • It applies jacoco-report-aggregation and test-report-aggregation at root project
  • Creates jacocoAggregatedReport and testAggregatedReport for TestSuiteType.UNIT_TEST
  • If a module applies jacoco plugin, it adds it to the jacocoAggregation and testReportAggregation root configurations
  • If a module applies the java plugin, makes its child jacocoAggregatedReport task to depend on test
  • If a module applies the android plugin:
    • it enables by default BuildType.enableUnitTestCoverage on debug to produce jacoco exec files
    • adds the codeCoverageExecutionData, codeCoverageSources, codeCoverageElements (classes) and testResultsElements outgoing variants, to allow jacoco-report-aggregation and test-report-aggregation to aggregate it

Please note that JVM still need to manually apply jacoco plugin (this is an intentional opt-in behavior) build.gradle.kts

Producing an aggregated report for the whole project

The task :jacocoAggregatedReport is added to the root project when applying this plugin and it can be run to produce the report. All dependent test tasks will be run too to produce the required execution data. Aggregated JaCoCo Report example

The same for :testAggregatedReport: Aggregated Test Report example

Enforcing aggregated code coverage metrics

The same as JaCoCo Plugin supports Enforcing code coverage metrics this plugin adds a ':jacocoAggregatedCoverageVerification' to provide the same feature, but with the aggregated metrics:

tasks.jacocoAggregatedCoverageVerification {
    violationRules {
        rule {
            limit {
                minimum = "0.5".toBigDecimal()
            }
        }

        rule {
            isEnabled = false
            element = "CLASS"
            includes = listOf("org.gradle.*")

            limit {
                counter = "LINE"
                value = "TOTALCOUNT"
                maximum = "0.3".toBigDecimal()
            }
        }
    }
}

The aggregateTestCoverage DSL extension

This is an opt-in/out switch meant to be used when having productFlavors.

enableUnitTestCoverage is a BuildType setting (default on debug). When having flavors, you'll have many coverage reports to produce targeting debug (one per flavor variant). You can use enableUnitTestCoverage.set(false) to turn aggregation off for an specific ProductFlavor. Basically, the variant won't be added to the codeCoverageExecutionData configuration, so :jacocoAggregatedReport won't compute it

For instance, app module has a environment dimension with 2 flavors: stage and prod. Without any extra settings, :jacocoAggregatedReport will depend on :app:testStageDebugUnitTest and :app:testProdDebugUnitTest (running its src/test/ tests effectively twice). You may choose which flavors participates in the aggregated report by doing:

    productFlavors {
        create("stage") { 
            dimension = "environment" 
        }
        create("prod") { 
            dimension = "environment"
            aggregateTestCoverage.set(false)
        }
    }

where it effectively only run :app:testStageDebugUnitTest

Note

The aggregateTestCoverage DSL applies for both :jacocoAggregatedReport and :testAggregatedReport tasks