Code coverage is the percentage of your code that runs when you execute your tests. That's it. And that's exactly the problem: a lot of developers confuse "my code ran" with "my code is tested".
What is code coverage?
When you run your unit tests or functional tests, a coverage tool records which lines were executed and which were not. 80% coverage means 20% of your code was never touched.
On a Python project, Coverage.py does this well. You run your tests, it produces a report, you see the gaps. Straightforward.
The catch is that a test can execute code without checking anything. I've seen projects at 95% coverage where half the tests had no assertions at all. The dashboard was green across the board, and bugs still made it to production.
The different types of coverage
There are several ways to measure coverage, and they don't tell the same story.
Line coverage
The most common. How many lines were executed? That's what most tools show by default. A React application tested with Jest might show 90% here, and still have entire if/else blocks where the "else" branch was never tested.
Branch coverage
This one is more interesting. It checks that every if/else, every switch, was taken in all its cases. On payment code, it's the difference between "the validator ran" and "we know what happens when the card is declined".
Path coverage
All possible combinations of branches within a function. The most thorough in theory. In practice, a function with 10 conditions has 1,024 possible paths, so forget about hitting 100%.
Function coverage
Was every function called at least once? That's the bare minimum. Handy for spotting dead code, but it says nothing about what happens inside.
What's the point?
Catching bugs before production
The obvious one. If your authentication component isn't tested, you'll find out when a user can't log in. Not great.
Seeing what nobody understands
Uncovered zones are often the ones the team avoids. Nobody tests them because nobody quite knows what they do. The coverage report makes that visible. API routes sitting at 0%? Either they're unused or they're a liability. Both need attention.
Refactoring without crossing your fingers
When a module has decent coverage, you can rewrite it knowing your tests will warn you if something breaks. Without that, every refactor is a roll of the dice.
Setting it up with Django
Three commands and you're done.
pip install coverageRun your tests with coverage tracking enabled:
coverage run manage.py testCheck the results:
coverage reportFor an HTML report you can browse file by file:
coverage htmlOpen htmlcov/index.html in your browser.

What coverage doesn't tell you
Coverage has a built-in blind spot: it measures what runs, not what's verified. A test that calls a function without checking its return value counts in the stats. It doesn't count in reality.
In practice:
- A test that checks whether the price including VAT with a 15% discount returns $42.50 is a real test. A test that calls
calculate_price()and ignores the result is padding. - Double down on the stuff that really hurts when it breaks. Authentication, payments, encryption. A payment module at 50% coverage is a problem waiting for its moment.
- No need to aim for 100% on a config file or a migration script. Save your energy for the code that has consequences when it gets things wrong.
Coverage is a map. It shows you the terrain. Where you go from there is up to you.




