Filling In the Gaps in Coverage
Goal
You learn not how to write tests but how to choose what to test. You use coverage as a map, not as a goal.
Why it matters
100% coverage guarantees nothing. Even assert x is not None alone
produces 100%. Conversely, the truly risky places (error handling, boundary values) are empty because writing tests
was too much trouble, and that is exactly why bugs live there.
In this lab you find those gaps by the numbers and fill them, and at the end you finish by deciding what you will not fill.
Environment
pytest --cov=<모듈> --cov-report=term-missing
pytest --cov-branch 분기 커버리지
pytest --durations=5 느린 테스트 다섯 개
There is no network, but pytest, coverage, and hypothesis are included in the image.
Steps
- A function with three branches →
/root/work/tt/billing.py - Test only the normal path →
test_billing.py - Check the missing lines →
/root/work/tt/03-cov.txt - Fill in the error paths
- Branch coverage →
/root/work/tt/05-branch.txt - Verify a property with hypothesis
- The slow test →
/root/work/tt/07-slow.txt - What not to test →
/root/work/tt/08-verdict.md
Notes
- The test file name must start with
test_for pytest to find it. - The name in
--cov=billingis a file name, not a path. - When hypothesis finds an input that fails, it shrinks it to the smallest counterexample and shows you.
Create the code to test
A function with three branches → /root/work/tt/billing.py
Create /root/work/tt/billing.py. One function is enough: discount(price, rate) should (1) raise ValueError if rate is outside 0 to 1, (2) raise ValueError if price is negative, and (3) otherwise return a rounded integer. With three branches, the coverage is easy to read.
The first test
Test only the normal path → test_billing.py
Create test_billing.py in the same folder and test just one normal path. Make it pass with pytest -q. Do not touch the error paths yet; in the next step coverage will point them out.
Look at the missing lines
Check the missing lines → /root/work/tt/03-cov.txt
Run pytest --cov=billing --cov-report=term-missing -q and save the result to /root/work/tt/03-cov.txt. The line numbers of the error-handling lines must appear in the Missing column. The answer is not the number but those line numbers.
Fill in the gaps
Fill in the error paths
Add tests for the two error paths. Use pytest.raises(ValueError). When you measure again, coverage should be 100%.
Branch coverage is different
Branch coverage → /root/work/tt/05-branch.txt
Measure again with pytest --cov=billing --cov-branch --cov-report=term-missing -q and save it to /root/work/tt/05-branch.txt. Even if line coverage is 100%, branch coverage may not be. Seeing that difference is the purpose.
Inputs people cannot think of
Verify a property with hypothesis
Write one property-based test with hypothesis. Use @given(st.integers(min_value=0, max_value=10**6), st.floats(min_value=0, max_value=1)) to verify a property such as "the result is always at most price". The difference is that you write down a rule instead of listing examples.
Pick out the slow tests
The slow test → /root/work/tt/07-slow.txt
Look at the five slowest with pytest --durations=5 -q. Save the result to /root/work/tt/07-slow.txt. A slow test ends up not being run, and a test that is not run is worse than none.
Decide what not to test
What not to test → /root/work/tt/08-verdict.md
Write three lines in /root/work/tt/08-verdict.md: (1) what must be tested in this module, (2) what you decided not to test, and (3) why. The conclusion of this lab is not to make 100% the goal.