Test coverage reports which code executes when an automated test suite runs. It can record lines, branches, statements, or functions depending on the language and tool. Coverage provides evidence about where automated checks exist, but it does not prove that those checks verify the right behaviour.
What coverage measures
A coverage tool observes execution. If a test calls a function, its lines may count as covered. If the test follows one side of a condition, the other branch can remain uncovered. Different measurements therefore answer different questions about the same suite.
| Coverage type | What it records | What it does not prove |
|---|---|---|
| Line coverage | Lines executed by tests | Correct outputs or assertions |
| Branch coverage | Decision outcomes exercised | Complete business scenarios |
| Function coverage | Functions called | Meaningful behaviour checked |
| Statement coverage | Statements executed | Safe interaction between components |
Coverage data must be read with the tests. A suite can execute code without checking its result. It can also protect critical behaviour with a small number of focused tests while leaving routine code uncovered.
Why legacy coverage is uneven
Older systems often contain code from different periods, teams, and testing practices. Core modules may have extensive unit tests while scheduled jobs, integration points, or user interface flows rely on manual checks. Some tests may no longer run in the current build or may depend on environments that are difficult to reproduce.
A legacy codebase audit checks whether the suite can run, what it covers, and what its assertions protect. It also identifies business-critical paths that lack reliable verification even when the overall report appears healthy.
How coverage informs refactoring
Coverage helps a team decide where to add a safety net before changing behaviour. The aim is to capture what the system currently does at the boundary being moved or rewritten.
A practical sequence is:
- Run the existing suite. Establish which tests are reliable in the current environment.
- Map critical behaviour. Connect business flows to the code that implements them.
- Find uncovered paths. Focus on paths affected by the planned change.
- Add verification. Use unit, integration, or end-to-end tests at the appropriate boundary.
- Change in small steps. Run the checks as each part moves.
Why the number misleads in generated code
When code and tests are generated together in the same pass, the expectation a test checks can be derived from the implementation instead of from the requirement. The test then describes what the code already does, including the parts that are wrong. The result is a suite that executes a large share of the codebase and asserts very little of consequence: assertions check that a function returned something rather than the right thing, and error branches are called but never verified.
Reading the suite is more informative than reading the percentage. Which assertions would fail if the behaviour changed? Are the paths that handle money, permissions and personal data covered by anything specific? Do the tests still pass when a deliberate fault is introduced? That last check is the fastest way to tell a real suite from a decorative one. The same discipline applies to dependency risk, and it is a large part of what separates vibe coding from agentic engineering.
What coverage cannot replace
Coverage does not reveal whether requirements are correct, whether production data contains unexpected cases, or whether two components share an undocumented assumption. Those questions need domain knowledge, runtime evidence, and architectural analysis.
Missing or unreliable tests can form part of technical debt, especially where teams avoid changing important code because they cannot verify the result. Coverage analysis helps scope that debt and supports the takeover of an unfamiliar codebase by showing where automated evidence exists and where review must supply it.