Code Coverage is a metric that shows what percentage of an application’s source code is executed during testing. It helps assess test quality, identify untested areas, and prioritize writing new tests. According to Atlassian, 2025, the optimal coverage level is 70–80% — above this threshold, testing costs begin to outweigh the benefits.
Key Takeaways
Code Coverage is a quantitative metric that determines which part of the application’s source code was executed during tests. It is expressed as a percentage and calculated as the ratio of executed lines/branches to the total. High coverage does not guarantee zero bugs, but it reduces the risk of missed errors.
Code coverage helps the team: find untested code areas, make decisions about test writing priorities, and track testing quality trends in CI/CD. In mobile development, coverage is especially important for business logic, data models, and repositories — layers where error probability is highest.
A common myth: “100% coverage = perfect quality.” In practice, 100% coverage is extremely rare and often comes at the cost of superficial tests. Effective coverage is not a race for a percentage but a strategic coverage of critical paths and edge cases. Coverage says nothing about the quality of the tests themselves: a test may pass but fail to verify the correctness of the result.
Several Code Coverage metrics exist, each measuring different aspects of testing. Line coverage is the simplest metric, showing the percentage of executed code lines. Branch coverage measures which if-else and switch branches were tested.
Line coverage counts each source code line as executed or not. If a line contains a conditional operator or loop, the line is considered executed if control reached it, even if not all branches were processed. This is the least strict metric but the most understandable for visual assessment.
Branch coverage evaluates whether all possible branches in the code were tested. For each if-else, both branches (true and false) are considered. For switch, each case is considered. Branch coverage is considered a stricter metric than line coverage and more often reveals untested scenarios.
| Metric | What It Measures | Difficulty to Achieve |
|---|---|---|
| Line | Percentage of executed code lines | Low |
| Branch | Percentage of executed branches (if/else, switch) | Medium |
| Function | Percentage of called functions and methods | Low |
| Condition | Percentage of logical sub-expressions (&&, ||) | High |
Path coverage is the strictest metric, requiring verification of all possible combinations of branches in a function. In practice, path coverage is rarely used due to the exponential growth in the number of combinations: a function with 10 branches has 1024 possible paths.
In mobile development, various tools are used to measure Code Coverage depending on the platform. For Android, the standard is JaCoCo (Java Code Coverage), which integrates with Gradle and supports both unit tests and instrumentation tests. For iOS, XCCov is used, built into Xcode.
JaCoCo generates reports in HTML, XML, and CSV formats. The HTML report visually highlights lines: green — executed, red — missed, yellow — partially executed. The XML report is compatible with SonarQube and other code analysis systems. JaCoCo supports class filtering: generated code, databinding, and BuildConfig can be excluded.
// build.gradle — JaCoCo configuration
android {
buildTypes {
debug {
testCoverageEnabled = true
}
}
}
// Generating JaCoCo report
task jacocoTestReport(type: JacocoReport) {
dependsOn 'testDebugUnitTest'
reports {
xml.enabled = true
html.enabled = true
}
}
XCCov is a built-in Xcode tool for measuring code coverage. It is enabled via Gather coverage data in the test scheme. XCCov supports coverage for Swift and Objective-C, generates reports in .xccovreport format, and integrates with CI via xcodebuild -enableCodeCoverage YES. Data is output to the console and can be exported in JSON.
For centralized coverage monitoring, platforms like SonarQube (code quality analysis + coverage), Codecov, and Coveralls are used. These services aggregate data from JaCoCo and XCCov, show trends, Quality Gates, and integrate with GitHub/GitLab via PR comments.
Improving Code Coverage requires a systematic approach: not “pumping percentages” but closing risks. The first step is to analyze the JaCoCo or XCCov report — identifying red (uncovered) classes. Priority: business logic → repositories → ViewModel → UI components.
Test-Driven Development (TDD) automatically ensures high coverage because tests are written before implementation. The process: red (write a failing test) → green (write minimal code) → refactor. TDD disciplines the developer, forcing them to cover edge cases and exceptional situations that often remain untested.
One parameterized test replaces dozens of ordinary ones. JUnit and XCTest support parameterization: @ParameterizedTest in JUnit 5, XCTestCase with testPerformanceExample in XCTest. Parameterization allows checking many input values without code duplication, significantly expanding branch and condition coverage.
// Parameterized test in Kotlin with JUnit 5
@ParameterizedTest
@ValueSource(strings = ["user@test.com", "admin@test.com", "test@example.com"])
fun `validate email returns true for valid addresses`(email: String) {
val result = EmailValidator.isValid(email)
Assertions.assertTrue(result)
}
The most common mistake is chasing the percentage without analyzing test quality. The team starts writing tests for the sake of tests: checking getters and setters, duplicating coverage at different levels, testing trivial methods. This yields a high percentage but does not improve real quality.
High Code Coverage can create a false feeling that the application is well tested. A test may execute a line of code but not verify the correctness of the result. For example: a test calls a discount calculation method but does not check the amount — the line is executed, coverage grows, but the bug is not found.
A typical mistake is testing only the “happy path” and ignoring edge cases: empty lists, null values, maximum numbers, incorrect formats. Most bugs occur at boundaries and exceptions. Branch coverage helps identify missed branches but does not guarantee checking boundary values.
Mutation Testing is a method for assessing test quality where mutations (artificial errors) are introduced into the source code, and it is checked whether the tests fail. Pitest is a popular mutation testing tool for Java and Kotlin. If tests do not fail on a mutation, they do not verify that particular condition.
Pitest creates mutants — modified copies of source code where, for example, > is replaced with >=, true with false, or a method call is removed. Then tests are run for each mutant. If tests pass — the mutant survived, meaning the tests do not cover that scenario. If tests fail — the mutant is killed, the test is valid.
// build.gradle — Pitest configuration
plugins {
id 'info.solidsoft.pitest' version '1.15.0'
}
pitest {
targetClasses = ['com.example.app.*']
targetTests = ['com.example.app.*Test']
threads = 4
outputFormats = ['HTML', 'XML']
mutationThreshold = 80
coverageThreshold = 85
}
Pitest supports many mutation types: changing conditional operators (== → !=, < → <=), removing method calls, replacing return values (true → false), changing arithmetic operations (+ → -), mutating increments (i++ → i--). The more mutation types are killed by tests, the more reliable the test suite.
The target mutation score is 80% and above. This means 80% of artificial errors are detected by tests. Code Coverage of 90% does not guarantee that tests find bugs — mutation testing provides a more objective assessment. Pitest can be integrated into CI as a Quality Gate, blocking the build if the mutation score drops below the threshold.
For automated Code Coverage control in CI/CD, Quality Gates are used — threshold values that, when violated, mark the build as unstable or reject it. SonarQube allows configuring a Quality Gate based on a combination of metrics: coverage (≥80%), number of bugs, vulnerabilities, and duplicated code.
In GitHub Actions, Code Coverage is integrated through action steps: run tests with coverage → upload report to Codecov → check threshold. Codecov automatically comments on PRs with a coverage diff, showing which lines changed and how it affected the overall percentage. If coverage dropped, the PR is blocked until additional tests are written.
# GitHub Actions — uploading coverage to Codecov
- name: Run Tests with Coverage
run: ./gradlew testDebugUnitTest jacocoTestReport
- name: Upload to Codecov
uses: codecov/codecov-action@v4
with:
files: ./app/build/reports/jacoco/jacocoTestReport.xml
flags: unittests
fail_ci_if_error: true
- name: Check Coverage Threshold
run: |
coverage=$(grep -oP 'branchCoverage="\K[0-9.]+' report.xml)
if (( $(echo "$coverage < 80" | bc -l) )); then
echo "Coverage $coverage% is below 80% threshold"
exit 1
fi
HTML reports from JaCoCo and XCCov contain visual coverage highlighting: green — executed lines, red — not executed. SonarQube additionally shows coverage at the file, class, method, and line level, as well as coverage history across sprints. This helps make decisions about refactoring and adding tests.
Frequently Asked Questions
For mobile projects, coverage of 70–80% for business logic and 50–60% for UI components is considered good. Above 80%, testing costs start to outweigh the benefits. It is important to remember that the percentage is not a goal but an indicator, and different modules may have different target levels.
Line Coverage shows how many lines of code were executed. Branch Coverage shows how many branches (if-else, switch) were tested. A line with if may be executed, but only the true branch may be tested, not the false one. Branch Coverage is stricter and reveals more missed scenarios.
In CI/CD, coverage is integrated through a Quality Gate: the build is blocked if coverage falls below the threshold. For Android, JaCoCo + SonarQube is used; for iOS, xcodebuild -enableCodeCoverage with .xccovreport parsing. GitHub Actions has ready-made actions for Codecov.
Yes, JaCoCo supports Jetpack Compose through the standard JVM coverage mechanism. However, Compose code contains many generated lambda expressions that JaCoCo may not fully cover. It is recommended to exclude generated compose code from the report using filters.
False coverage occurs when a test executes code but does not verify the result. Solution: write assert checks for every important scenario, use mutation testing (Pitest) to verify test quality, and analyze not only the percentage but also which branches are covered.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also