Measure Your Code’s Reach: Integrating JaCoCo Code Coverage in Android Apps with Kotlin DSL

Reena Rote
5 min readFeb 25, 2024

--

Improving code quality is a constant pursuit for Android developers. Unit tests and UI tests are crucial in this journey, but how do you measure the effectiveness of these tests? Here’s where JaCoCo and Kotlin DSL come in, providing an efficient way to integrate code coverage for both unit and UI tests in your Android app.

What is JaCoCo?

JaCoCo is a free and open-source library that analyzes bytecode and identifies executed lines during test execution. This data is then used to generate reports, displaying how much of your code your tests actually cover.

Why Use JaCoCo in Your Android App?

Integrating JaCoCo offers several benefits:

  • Identify untested code: JaCoCo reveals sections of code untouched by your tests, highlighting areas needing test coverage improvement.
  • Track code coverage trends: Monitoring coverage over time allows you to evaluate the effectiveness of your testing efforts and identify potential regressions.
  • Maintain code quality: By aiming for high code coverage, you can ensure your tests are thorough and likely to catch bugs early.

Integrating JaCoco with Kotlin DSL

Now, let’s see how to integrate JaCoco using Kotlin DSL:

1. Add Jacoco plugin to your app/build.gradle file

plugins {
jacoco
}

2. Enable coverage for UI and Unit Tests in android{ ... } block of app/build.gradle

android {

buildTypes {
debug {
enableAndroidTestCoverage = true
enableUnitTestCoverage = true
}
}

3. Now prepare your jacoco exclusion list

Code coverage tools like JaCoCo are used to measure how much of your source code is exercised by your tests. However, there are certain files or packages that you typically don’t want to include.

The exclusions variable should be defined outside of the android { ... } block.

val exclusions = listOf(
"**/R.class",
"**/R\$*.class",
"**/BuildConfig.*",
"**/Manifest*.*",
"**/*Test*.*"
)

By specifying exclusions list, you ensure that, only relevant source code files are considered in the coverage analysis, providing a more accurate picture of the effectiveness of your tests in exercising your actual codebase.

4. Lets configure a Test tasks out side of android { ... }:

The provided block of code configures Jacoco tasks for tests. Let’s break down its purpose:

tasks.withType(Test::class) {
configure<JacocoTaskExtension> {
isIncludeNoLocationClasses = true
excludes = listOf("jdk.internal.*")
}
}

isIncludeNoLocationClasses = true: This setting instructs Jacoco to include classes that have no source location information in the coverage report. These are typically classes generated dynamically or by the JVM, and including them can provide more accurate coverage information.

excludes = listOf("jdk.internal.*"): This specifies a list of classes or packages to exclude from the coverage analysis. In this case, it excludes classes from the jdk.internal package.

Without the exclusion jdk.internal.*, your Jacoco execution might encounter errors like java.lang.ClassNotFoundException: jdk.internal.reflect.GeneratedSerializationConstructorAccessor1.

5. Configure Reports

Lets configure a task to generate reports from collected coverage data:

android {
// Iterate over all application variants (e.g., debug, release)
applicationVariants.all { variant ->
// Extract variant name and capitalize the first letter
val variantName = variant.name.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
}

// Define task names for unit tests and Android tests
val unitTests = "test${variantName}UnitTest"
val androidTests = "connected${variantName}AndroidTest"

// Register a JacocoReport task for code coverage analysis
tasks.register<JacocoReport>("Jacoco${variantName}CodeCoverage") {
// Depend on unit tests and Android tests tasks
dependsOn(listOf(unitTests, androidTests))
// Set task grouping and description
group = "Reporting"
description = "Execute UI and unit tests, generate and combine Jacoco coverage report"
// Configure reports to generate both XML and HTML formats
reports {
xml.required.set(true)
html.required.set(true)
}
// Set source directories to the main source directory
sourceDirectories.setFrom(layout.projectDirectory.dir("src/main"))
// Set class directories to compiled Java and Kotlin classes, excluding specified exclusions
classDirectories.setFrom(files(
fileTree(layout.buildDirectory.dir("intermediates/javac/")) {
exclude(exclusions)
},
fileTree(layout.buildDirectory.dir("tmp/kotlin-classes/")) {
exclude(exclusions)
}
))
// Collect execution data from .exec and .ec files generated during test execution
executionData.setFrom(files(
fileTree(layout.buildDirectory) { include(listOf("**/*.exec", "**/*.ec")) }
))
}
}
}

This script defines a Jacoco report task for each build variant. It executes both unit tests and Android instrumented tests (UI tests) associated with each variant and generates a combined Jacoco coverage report.

The report includes both XML and HTML formats. Source directories are set to the main source directory, and class directories are set to the compiled Java and Kotlin classes, excluding any specified exclusions.

Finally, execution data is collected from the .exec and .ec files generated during test execution.

6. Generating Reports

Option 1: Using the Terminal, execute the following command:

./gradlew JacocoDebugCodeCoverage

Option 2: Using the Gradle Task, navigate to Gradle -> Tasks -> reporting -> JacocoDebugCodeCoverage.

7. Viewing the reports

Once your tests are executed, JaCoCo will generate coverage reports in XML and HTML formats. You can view these reports to analyze your code coverage and identify areas for improvement. The HTML report, in particular, provides a detailed overview of your codebase, highlighting covered and uncovered lines of code.

After generating the Jacoco coverage reports, you can analyze them to gain insights into the effectiveness of your tests. These reports highlight the percentage of code covered by tests, pinpointing areas that require more attention.

When covering Jetpack Compose classes with code coverage tools like Jacoco, there are several known issues and challenges developers may encounter:

  1. Dynamic UI Generation: Jetpack Compose allows UI elements to be generated dynamically based on state changes. Code coverage tools may have difficulty tracking coverage accurately in such scenarios, as the UI structure is not predefined.
  2. Lambda Expressions: Jetpack Compose heavily utilizes lambda expressions for defining UI components and event handlers. These anonymous functions might not be fully covered by tests, leading to lower coverage metrics.
  3. State Management: Jetpack Compose relies on state management techniques such as State and ViewModel. Achieving full coverage for state-related logic can be challenging due to the dynamic nature of state changes.
  4. Custom Composable Functions: Developers often create custom composable functions for reusable UI components. Ensuring comprehensive coverage for these custom components can be complex, especially when they’re deeply nested or heavily parameterised.
  5. Animation and Interaction: Code coverage tools may struggle to accurately assess code paths triggered by animations or user interactions in Jetpack Compose. These dynamic behaviours can result in missed coverage.
  6. View Model Composition: Jetpack Compose encourages the composition of small, reusable ViewModels. While unit testing these ViewModels is essential, achieving high coverage can be challenging due to the complex interactions between ViewModels and UI elements.
  7. Experimental Nature: Jetpack Compose is still evolving, with new features and APIs being introduced regularly. Code coverage tools may not fully support all Jetpack Compose features or may encounter compatibility issues with newer versions.

In the end, integrating JaCoCo into your Android project isn’t just about numbers and statistics — it’s about building better apps that users can rely on. So, if you’re serious about delivering top-notch Android experiences, JaCoCo is definitely worth exploring. Happy coding and testing!

Please don’t hesitate to ask if you have any questions.

Github

--

--