Reliability and CI

Use coverage as a map

Read line, branch, and function coverage to find unexamined code without treating one percentage as proof of quality.

Coverage tells you which lines of code ran while the tests ran. That’s all. It doesn’t say the assertions were useful, or that the requirements were right. I treat it as a map of where I haven’t looked yet, not as a score.

Run it

The Node test runner collects coverage with one flag:

node --test --experimental-test-coverage

At the end of the normal output you get a table:

ℹ start of coverage report
ℹ file           | line % | branch % | funcs % | uncovered lines
ℹ books.js       |  92.31 |    66.67 |  100.00 | 14
ℹ routes.js      |  88.00 |    50.00 |  100.00 | 22-23

Three numbers per file. Line coverage says which lines ran. Function coverage says which functions were called. Branch coverage says which paths through an if were taken, and it’s the one I read first.

Why branches matter

One line can hide several outcomes. Take this route:

if (!book) return json({ code: 'not-found' }, 404)
return json(book, 200)

A happy-path test runs the function, and line coverage looks fine. But the 404 branch never ran. Branch coverage shows 50% and points at it.

The next question isn’t “how do I turn that line green?”. It’s “what user-visible failure is untested?”. Here it’s asking for a book that doesn’t exist. So send a request for an absent ID and assert the 404 contract. The coverage goes up as a side effect.

Coverage lies too

A test can run both branches and assert nothing meaningful. Coverage is 100% and the code is unprotected.

My quick audit is a small mutation. Change the 404 to 200, or flip the condition, and run the suite. If it stays green, execution was measured but behavior wasn’t checked. Fix the test, not the number.

Don’t chase one percentage

A parser with many decisions deserves dense branch coverage. A thin adapter around a database driver is better protected by a few real integration tests than by unit tests that mock the driver.

Thresholds are useful for one thing: stopping a big accidental drop. Set them after you understand the baseline, not before. And if you exclude generated or unreachable code, write down why, so the report stays honest.

Try this: run coverage on the Books API, pick one untested error branch, and write a behavior-focused test for it. Then mutate the branch and confirm your new test fails for the public outcome, not for an internal detail.

Lesson completed