有效覆盖率需覆盖所有公开接口、边界条件及错误输入路径,而非追求100%;应关注--cov-report=term-missing中标红的missing行,避免测试私有方法、__init__.py和类型提示。

pytest 要覆盖哪些代码才算“有效”?
覆盖率本身不等于质量,pytest 配合 pytest-cov 统计的是「被执行过的行数占比」,但容易误判:比如 if False: 下的分支、未触发的异常路径、装饰器生成的代码,都可能被计入「未覆盖」——其实它们本就不该运行。关键不是冲 100%,而是确保所有公开接口、边界条件、错误输入路径都被显式调用过。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
pytest --cov=my_module --cov-report=term-missing启动,重点关注报告里标红的「missing」行,它们才是真问题 - 避免测试私有方法(以
_开头),除非它逻辑复杂且无法通过公有接口触达 -
__init__.py和类型提示(from __future__ import annotations)默认不计入覆盖率,无需强行覆盖
如何让 pytest 自动发现并运行测试用例?
Pytest 默认只识别满足命名约定的文件和函数:test_*.py 或 *_test.py 文件里的 test_* 函数,或继承自 unittest.TestCase 的类。一旦命名不匹配,pytest 就直接跳过——连报错都不会有,这是新手最常踩的静默坑。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 测试文件必须以
test_开头或_test结尾,例如test_utils.py✅,utils_test.py✅,utils.py❌ - 测试函数名必须是
test_*,例如test_parse_json()✅,check_json()❌(即使加了@pytest.mark.parametrize也无效) - 如果项目结构深(如
src/myapp/),在根目录运行时需加--rootdir=.并确认pyproject.toml中[tool.pytest.ini_options]没有错误的testpaths
参数化测试怎么写才不漏边界值?
@pytest.mark.parametrize 是补全覆盖的关键,但很多人只传两三个典型值,漏掉 None、空字符串、负数、极大值等真正容易崩的输入。更隐蔽的问题是:当参数组合爆炸时(比如 3 个参数各 4 种取值),盲目全排列会让测试变慢且冗余。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 每个参数单独标记,用
ids显式命名用例,方便定位失败项:@pytest.mark.parametrize("value,expected", [(1, True), (0, False), (None, False)], ids=["positive", "zero", "none"]) - 对多参数组合,优先用
itertools.product手动控制组合范围,而不是依赖parametrize的笛卡尔积 - 必测的「坏数据」:空值(
None,"",[])、类型错位(传int却要str)、超长字符串(>1024 字符)、带 BOM 的 UTF-8 字节流
mock 外部依赖后,为什么覆盖率反而下降了?
用 patch 替换数据库调用或 HTTP 请求时,如果 mock 写在测试函数内部(比如 with patch(...)),而被测函数里原本有 try/except 包裹真实调用,那么 except 分支就永远不会执行——pytest-cov 会把它标为「未覆盖」。这不是 bug,是 mock 隐蔽地删掉了执行路径。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- mock 应尽量贴近真实调用位置,比如函数内调用了
requests.get,就 patchmy_module.requests.get,而不是 patchrequests.api.get - 对异常路径,必须显式触发:在 mock 对象上设
side_effect=ValueError("boom"),再断言是否进了 except 块 - 避免 patch 过深(如
builtins.open),优先改被测函数设计,把 IO 抽成可注入的参数,测试时直接传入 StringIO 或 dict
覆盖率数字只是镜子,照出你没想清楚的地方。真正难的从来不是让百分比变高,而是判断哪一行「不该被覆盖」、哪一分支「永远不该执行」——那才是设计意图的真相。


















