Android Multi-Module Code Coverage with Kover.

Lakshmikanth Dhanaraj · March 29, 2024

Code coverage, often expressed as a percentage, serves as a crucial metric in software development, indicating the extent to which test cases exercise a codebase. While it doesn’t evaluate the quality of individual tests directly, it offers valuable insights into areas that might require more testing attention, thereby guiding overall test suite enhancement.

Kover, an official open-source solution incubated by JetBrains, tailored for Kotlin-centric projects. With its user-friendly setup and the ability to aggregate coverage data across project modules, Kover empowers developers with comprehensive insights into their test coverage landscape.

Setting up Kover is straightforward, requiring only a few simple steps to integrate the Gradle plugin into your project modules.

Begin by including the Kover plugin dependency in the root-level build.gradle file:

buildscript { 
	repositories { 
		mavenCentral()
	 } 
	dependencies {
		 classpath("org.jetbrains.kotlinx:kover-gradle-plugin:0.7.6") 
	}
 }

Next, apply the Kover plugin in the build.gradle file of each module where code coverage metrics are required:

apply plugin: 'org.jetbrains.kotlinx.kover'

By applying this plugin, Kover automatically generates dedicated Gradle tasks for each build variant (Build type + Flavor name). These tasks handle the following:

  • Verification: Verifying code coverage against any predefined thresholds you may have set.
  • Report Generation: Generating HTML and XML reports for visualization and integration with other tools.
  • Logging: Logging code coverage metrics for further analysis.

Verification Task:

The verification task in Kover allows you to ensure that the code coverage value falls within a configured range, failing the build if the coverage value is outside this range.

To configure the allowable coverage range, you can use the koverReport Gradle extension:

koverReport {
    verify {
        rule {
            minBound(40)
        }
    }
}

In this snippet, minBound is set to 40, indicating that the build will fail if the code coverage value is not greater than 40% when running the gradlew koverVerifyDebug task.

You can also specify the range independently for each build variant:

koverReport {
    verify {
        rule {
            minBound(40)
        }
    }
    androidReports("debug") {
        verify {
            rule {
                minBound(0)
            }
        }
    }
}

In this example, we’re allowing 0% coverage for debug builds and a minimum of 40% coverage for other build variants.

Pre-Commit Hooks:

Running the koverVerify task in the Git pre-commit hook is an effective way to enforce code coverage standards before committing code to the repository. This ensures that code lacking sufficient unit tests is not accepted into the repository, maintaining code quality and test coverage standards.

Report Generation Task:

Kover provides the capability to generate both XML and visually appealing HTML code coverage reports, illustrating the lines of code covered and uncovered by unit tests.

To generate these reports, you can execute either the gradlew koverHtmlReportDebug or gradlew koverXmlReportDebug tasks. Upon completion, the reports will be generated and stored in the app/build/reports¸ directory.

sample HTML report

Logging Task:

The logging task in Kover provides a straightforward way to print the code coverage value in the build log. By executing the logging task, Kover will simply print the coverage value in the build log, allowing developers to easily track the progress of code coverage improvements over time.

sample coverage log

Integrating with GitLab Merge Requests and Pipeline Badges:

The logging task becomes particularly useful when integrated into GitLab pipelines for monitoring code coverage in Merge Requests (MRs) or displaying coverage badges in pipeline statuses.

To extract the code coverage value printed by Kover in the job output, you can use the coverage keyword in the GitLab pipeline configuration. Below is a sample GitLab YAML configuration demonstrating how to extract code coverage:

unitTestJob:
  script:
    - gradlew koverLogRelease
  coverage: '/application line coverage: \d+(?:\.\d+)?/'

In this configuration, the koverLogRelease task is executed within the CI pipeline job. The coverage keyword then extracts the code coverage value from the job output using a regular expression pattern.

Filtering Reports:

By default, Kover includes all packages and classes in the project when generating code coverage metrics. However, it’s often unnecessary to include generated classes like R, BuildConfig, or Databinding classes in these reports. Fortunately, Kover provides options to exclude or include specific packages or classes while calculating code coverage metrics.

Global Filtering: To exclude specific packages or classes globally across all build variants, you can use the configuration of the filter within the koverReport block:

koverReport {
    filters {
        excludes {
            classes("me.dlkanth.koverdemo.databinding.*")
        }
    }
}

In this snippet, all classes in the package “me.dlkanth.koverdemo.databinding” are excluded from code coverage calculations for all build variants.

Variant-Specific Filtering: Alternatively, you can define filtering for specific build variants:

koverReport {
    androidReports("release") {
        filters {
            excludes {
                classes("me.dlkanth.koverdemo.databinding.*")
            }
        }
    }
}

In this example, the exclusion is configured only for the release build variant. This allows for finer control over which packages or classes are included or excluded based on the build variant being used.

Configuring for Multi-Module Architecture:

In modern Android development, modular architecture is prevalent, where projects are divided into multiple modules for better organization and maintainability. While applying the Kover plugin to individual modules allows for generating coverage reports for each module separately, it’s often more desirable to obtain a holistic view of code coverage across all modules.

Thankfully, Kover offers a solution for aggregating code coverage reports from multiple modules. By adding all modules as dependencies to the root module using a special Kover dependency, we can obtain an aggregated code coverage report encompassing all modules.

Sample Multi-Module Architecture: Consider a project structure with the following modules:

-- App 
	-- A 
	-- B

To obtain an aggregated code coverage report for all modules (App, A, and B)

  • Ensure that the Kover plugin is applied to all modules involved (App, A, and B).
  • Add special Kover dependencies to modules A and B in the App module’s build.gradle file:
    dependencies {
      implementation(project(":A"))
      implementation(project(":B"))
    
      kover(project(":A"))
      kover(project(":B"))
    }
    

Once the dependencies are set up, running koverHtmlReport or koverLog in the App module will generate an aggregated coverage report encompassing all modules (App, A, and B).

gradlew :app:koverHtmlReportDebug

That is all about Kover for Android.

Twitter, Facebook