Skip to content
Innovialabs

What Is Code Coverage and How Much Do You Really Need

Basics4 min

In short

Code coverage shows which parts of the code ran during tests, not whether they were checked; use it to find untested areas, require coverage on new code in CI, and set a moderate floor instead of chasing 100%.

What code coverage measures

Code coverage is the share of your code that was executed while the test suite ran. A coverage tool instruments the program, runs the tests and reports which lines, branches and functions were reached.

The key point: coverage tells you what ran, not what was verified. A test that calls a function without a single assertion still raises coverage.

Main types of coverage

TypeWhat countsWeak spot
Line (statement)Each executed lineA line with if a and b counts as covered even if one condition was never false
BranchEach outcome of every decision: both sides of if, every case, loop entered and skippedDoes not check every combination of conditions
FunctionEach function called at least onceOne call says almost nothing about edge cases

Branch coverage is the most useful everyday metric. It catches the classic gap: the else path, the error handler, the early return that tests never reach.

def discount(user):
    if user.is_vip:
        return 0.2
    return 0.0

A single test with a VIP user leaves the last line unexecuted and covers only one of the two branches. A test for a regular user closes both gaps.

Tools by ecosystem

  • Python — coverage.py. Run coverage run --branch -m pytest, then coverage report or coverage html. With pytest, the pytest-cov plugin gives pytest --cov=src --cov-branch.
  • JavaScript and TypeScript — Istanbul and c8. Istanbul (via nyc) instruments code; c8 uses the coverage built into the V8 engine. Jest has --coverage built in, and Vitest supports both V8 and Istanbul providers.
  • Java and Kotlin — JaCoCo. Works as a Java agent and plugs into Maven and Gradle; reports line, branch and other counters.
  • Others. Go has go test -cover, .NET commonly uses Coverlet, and most languages have a standard option.

All of them can output reports in common formats such as Cobertura XML or LCOV, which CI services and code review tools can display.

Why 100% coverage is misleading

  • Execution is not verification. Tests without meaningful assertions produce full coverage and catch nothing.
  • It ignores missing code. Coverage measures what you wrote. A forgotten validation or an unhandled case has no lines to cover.
  • Combinations escape it. Every line and branch can be covered while a specific combination of inputs still fails.
  • It distorts priorities. Teams start testing getters, generated code and trivial wrappers to hit a number, while complex logic gets shallow tests.
  • Cost grows sharply at the end. The last few percent are usually error paths that are hard to trigger and sometimes better tested at another level.

High coverage is a reasonable side effect of good tests. As a goal on its own, it rewards the wrong behaviour.

Sensible thresholds

There is no universal number. Instead of one figure for the whole project, think in layers:

  • A moderate global floor that the build must not fall below, chosen from where your project is today. A common starting point is somewhere around 70–80%, adjusted to the project.
  • Higher expectations for critical code: payments, permissions, data migrations, business rules.
  • Lower expectations for glue code: configuration, simple adapters, UI layout.
  • Exclude what should not count: generated files, migrations, test helpers. Keep exclusions explicit and reviewed.
  • Ratchet, do not jump. Raise the floor gradually as coverage improves rather than forcing a big target at once.

Coverage in CI

  1. Run tests with coverage on every pull request and upload the report as a build artifact.
  2. Fail the build below the floor. coverage.py supports --fail-under, Jest uses coverageThreshold, c8 has --check-coverage, JaCoCo has a check goal with rules.
  3. Check coverage of changed code. The most practical rule: new and modified lines must be covered. Tools like diff-cover or code review integrations can show this directly in the pull request.
  4. Review uncovered lines, not the percentage. The report's real value is pointing at the branches nobody tested.
  5. Combine with other signals. Mutation testing, code review and production errors tell you whether tests actually catch bugs.
# pyproject.toml
[tool.coverage.run]
branch = true
source = ["src"]

[tool.coverage.report]
fail_under = 75
show_missing = true

FAQ

It is stricter and more useful. It shows untested decision paths that line coverage hides, and most tools can report it with a single flag.

We can help with this

If you'd rather get the result than figure it all out yourself — this is what we do every day.

Discuss a project

Read next

Have an idea or a task? Tell us about it

We'll reply within one business day and propose a solution. The consultation is free.