把覆盖率的空白填上
目标
学习的不是如何编写测试,而是如何选择要测试的内容。 把覆盖率当作地图,而不是目标。
为什么重要
100% 覆盖率不能保证任何事情。仅有 assert x is not None 也能达到 100%。
相反,真正危险的部分(错误处理、边界值)因为难写测试而保持空白,bug 正因如此存活。
本实验将用数字找到这些空白并补上,最后以决定哪些地方不测试结束。
环境
pytest --cov=<모듈> --cov-report=term-missing
pytest --cov-branch 분기 커버리지
pytest --durations=5 느린 테스트 다섯 개
虽然没有网络,但镜像中已包含 pytest、coverage 和 hypothesis。
步骤
- 有三个分支的函数 →
/root/work/tt/billing.py - 只测试正常路径 →
test_billing.py - 确认缺失行 →
/root/work/tt/03-cov.txt - 补上错误路径
- 分支覆盖率 →
/root/work/tt/05-branch.txt - 使用 hypothesis 验证性质
- 慢测试 →
/root/work/tt/07-slow.txt - 决定不测试的内容 →
/root/work/tt/08-verdict.md
参考
- 测试文件名必须以
test_开头,pytest 才能发现它。 --cov=billing中的名称是文件名,不是路径。- hypothesis 找到失败输入后,会缩减为最小反例并显示出来。
创建要测试的代码
有三个分支的函数 → /root/work/tt/billing.py
创建 /root/work/tt/billing.py。一个函数即可——discount(price, rate) 要做到:(1) rate 不在 0~1 范围时抛出 ValueError;(2) price 为负数时抛出 ValueError;(3) 其他情况返回四舍五入后的整数。三个分支很适合观察覆盖率。
第一个测试
只测试正常路径 → test_billing.py
在同一目录创建 test_billing.py,只测试一条正常路径,并用 pytest -q 使其通过。暂时不要处理错误路径——下一步让覆盖率指出它们。
查看遗漏的行
确认缺失行 → /root/work/tt/03-cov.txt
运行 pytest --cov=billing --cov-report=term-missing -q,并把结果保存到 /root/work/tt/03-cov.txt。Missing 列应显示错误处理对应的行号。答案不是覆盖率数字,而是那些行号。
补上空白
补上错误路径
为两条错误路径增加测试,使用 pytest.raises(ValueError)。再次测量时,覆盖率应达到 100%。
分支覆盖率并不相同
分支覆盖率 → /root/work/tt/05-branch.txt
用 pytest --cov=billing --cov-branch --cov-report=term-missing -q 重新测量,并保存到 /root/work/tt/05-branch.txt。即使行覆盖率是 100%,分支覆盖率也可能不是 100%——目标就是看清这个差异。
人想不到的输入
用 hypothesis 验证性质
用 hypothesis 编写一个 property-based test。通过 @given(st.integers(min_value=0, max_value=10**6), st.floats(min_value=0, max_value=1)) 验证“结果始终不大于 price”等性质。区别在于写出规则,而不是罗列示例。
找出慢测试
慢测试 → /root/work/tt/07-slow.txt
用 pytest --durations=5 -q 查看最慢的五个测试,并把结果保存到 /root/work/tt/07-slow.txt。慢测试会逐渐没人运行,而不运行的测试比没有测试更糟。
决定哪些内容不测试
决定不测试的内容 → /root/work/tt/08-verdict.md
在 /root/work/tt/08-verdict.md 中写三行:(1) 本模块必须测试的内容;(2) 决定不测试的内容;(3) 原因。本实验的结论是不把 100% 当作目标。