Concept
What is Test Coverage?
Test Coverage is a measure of how much of your source code is executed when your test suite runs. It is calculated by instrumentation tools (like Istanbul or V8) that inject counters into your code before executing tests.
Coverage reports break down into four primary metrics:
- Statement Coverage: Percentage of executable statements that ran.
- Branch Coverage: Percentage of decision branches (e.g.
if/elsepaths, switch cases, ternary checks) that were evaluated. - Function Coverage: Percentage of functions called.
- Line Coverage: Percentage of source code lines executed.
----------------------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
----------------------|---------|----------|---------|---------|
All files | 87.5 | 75.0 | 90.9 | 87.5 |
cart.ts | 95.2 | 85.7 | 100.0 | 95.2 |
----------------------|---------|----------|---------|---------|The 100% Coverage Trap
A common mistake in software teams is establishing a strict mandate requiring 100% test coverage. While high numbers sound safe, they have diminishing returns and can hide bad testing practices:
- Coverage != Correctness: A line of code that executes during a test counts as covered, even if the test does not contain any assertions to verify its output (e.g. testing without check statements).
- Brittle tests: Forcing developers to write tests for trivial elements (like boilerplate getter methods or standard styles) leads to low-quality tests that slow down development and break on minor code shifts.
Common Mistakes
1. Believing 100% statement coverage means bug-free code
Statement coverage tracks if a line executes, but misses logical branches. If you have const ratio = a / b; and you test it with b = 2, the statement is 100% covered. However, if a user passes b = 0, the application crashes with a division-by-zero or returns Infinity because the boundary path was never tested.
2. Not ignoring generated or config files in coverage metrics
Including compiler outputs, build build scripts, lint configs, or index imports in coverage calculations degrades your overall metrics. Always ignore these files in your test runner config settings:
// vitest.config.ts
export default defineConfig({
test: {
coverage: {
exclude: ['node_modules/', 'dist/', '**/*.config.js']
}
}
});Best Practices
- Prioritize Critical Paths: Aim for high coverage (e.g., 90%+) on core business logic (cart updates, payment routes, permissions) while permitting lower numbers on visual layouts.
- Enforce Branch Coverage: Focus on Branch Coverage over Statement Coverage, ensuring that all condition paths (true and false states) are tested.
- Use coverage as a guide, not a goal: Treat coverage maps as diagnostic overlays to identify untested, complex areas of your codebase, rather than a metric to judge developer performance.
