Should I test that?

Should I aim for 100% test coverage?

Verdict

No

No, do not aim for 100% test coverage in a product codebase; test the code where a failure costs money, access or stored data, and read the coverage report for untested lines of that kind.

Why

No, do not aim for 100% coverage; test by risk and use the coverage report to find risky lines that no test runs. The typical case is a team whose tests cover the main paths, asking whether to test the rest: wiring, logging and rare error branches. Blast radius is users, Change frequency is rarely, and Detectability is same-day, because such a failure throws or breaks a page that a user reports. Reversibility is trivial, since a redeploy fixes it, and Test cost is moderate, since each test mocks a rare failure and breaks on refactors. Rule R13 gives Do not test, on the edge: one step toward risk in any factor gives Test minimally for that line.

When the decision changes
WhenDecisionWhy
An uncovered branch calculates a partial refundTest mandatory, with amounts that hit each rounding caseBlast radius rises to money and Reversibility to costly
An uncovered branch denies a request for another tenant's recordTest mandatory: one allowed and one denied requestBlast radius rises to safety-or-legal and Detectability to never, because a leaked record raises no error
A standard or a contract requires coverage evidence, such as MC/DC under DO-178C for avionicsTest mandatory, to the coverage the standard namesBlast radius rises to safety-or-legal, and the framework puts required evidence first
An uncovered catch block returns an empty list when a read fails, and nothing logs the failureTest minimally: one test that forces the failure and asserts the resultDetectability moves to eventually, because the fallback looks like a normal result
The uncovered lines hold delivery date rules that the team changes monthlyTest the main path and the likeliest edge cases in CIChange frequency rises to regularly, Detectability to eventually and Reversibility to with-effort
An uncovered fallback serves cached data on a third-party timeout, and forcing the timeout takes days of setupTest it differently: alert on the fallback rate in productionTest cost rises to heavy and Detectability to eventually, while Reversibility stays trivial

What breaks if you don't test

For the typical untested remainder, a failure shows the same day: a misspelled config key stops a job, a user reports a broken page, and a redeploy ends it. The damage comes when the uncovered list hides a refund branch or an access check, which fail silently. A percentage does not show which kind of line is missing; the line-by-line report does.

What you lose if you over-test

The last lines cost the most: mocks that throw on the third call, tests for toString(), tests that call main, all breaking when a refactor moves a line. A gate also counts execution, so a test that asserts nothing raises coverage as much as a real one:

import json

DEFAULTS = {"theme": "light", "page_size": 20}

def load_settings(path):
    try:
        with open(path) as f:
            return json.load(f)
    except FileNotFoundError:
        return DEFAULTS

# Written for the gate: covers the except branch, checks nothing
def test_load_settings_missing_file(tmp_path):
    load_settings(tmp_path / "missing.json")

# Checks what the branch returns
def test_missing_file_gives_defaults(tmp_path):
    assert load_settings(tmp_path / "missing.json") == DEFAULTS

Only the second test fails if the branch returns {}. Martin Fowler writes in Test Coverage that he would be suspicious of anything like 100%.

What to do instead

  1. Test the main paths and every kind of line in the conditions table.
  2. Read the uncovered lines in the report, not the percentage, and run the five factors on each block.
  3. Exclude generated code and startup wiring, with # pragma: no cover in coverage.py or your tool's equivalent.
  4. For a gate, set a floor at today's number and stricter thresholds on risky folders: Jest's coverageThreshold accepts per-path keys, so ./src/billing/ can require 100% of branches.

When the answer changes

  • The code is a library or engine that many products embed, and its branches guard stored data.
  • A standard or a contract names a coverage figure.
  • The uncovered lines include money, access checks, or silent fallbacks.

Counterexample + Code example

Where 100% is right: SQLite

The SQLite core has 100% branch coverage and 100% MC/DC, with 590 times as much test code as library code (How SQLite Is Tested). The framework agrees: take the branch that recovers from a failed disk write. Blast radius is users: SQLite holds app data on every Android and iOS device (Most Widely Deployed). Change frequency is constantly, with check-ins on most days. Detectability is eventually, because a bad recovery leaves a file that fails on a later read. Reversibility is impossible, because data lost from a corrupted file often has no backup. Test cost is heavy, because the failure needs fault injection. The result is Test, since Reversibility at impossible keeps rule R9 from firing. Nearly every branch of a storage engine scores that way, so 100% there is the sum of per-branch decisions.

FAQ

Is 100% code coverage worth it?

No, 100% coverage is not worth it for a typical product codebase, because its last lines are wiring and rare error branches whose failures show the same day. It is worth it where every branch guards money, access or stored data, as in a billing module or SQLite.

What is a good code coverage percentage?

No coverage percentage is good on its own, because a test with no assertion raises it as much as a test that checks behaviour. Decide what to test next from the uncovered lines.

Should code coverage block a merge in CI?

Yes, a coverage gate can block a merge when coverage falls below today's level or a billing or access folder loses coverage. A global gate at 100% blocks merges over untested wiring, and developers meet it with tests that assert nothing.

Does 100% coverage mean the code has no bugs?

No, 100% coverage means every line ran during tests; it says nothing about whether a test checked the result. Mutation testing shows whether your tests fail when the code changes.