subtests能解决多输入断言失败中断问题,使每个断言独立运行、失败不中断且报告清晰。通过subtests.fixture配合with块实现,支持与parametrize混用,避免嵌套混乱,提升调试效率。

subtests能解决什么问题
测试里一堆相似断言,比如对多个输入值做同一类校验,传统写法要么重复写assert,要么用for循环——但后者一旦某个断言失败,整个测试就中断,看不到后续结果。用pytest-subtests可以把每个断言变成独立子测试,失败不中断,还能在报告里看到每条的输入和状态。
安装与基础用法
先装插件:pip install pytest-subtests。不需要额外配置,导入后直接用subtests fixture:
def test_parse_status_codes(subtests):
cases = [
("200 OK", 200),
("404 Not Found", 404),
("500 Internal Server Error", 500),
]
for raw, expected in cases:
with subtests.test(msg=f"parsing {raw!r}"):
assert parse_status(raw) == expected
pip install pytest-subtests。不需要额外配置,导入后直接用subtests fixture:
def test_parse_status_codes(subtests):
cases = [
("200 OK", 200),
("404 Not Found", 404),
("500 Internal Server Error", 500),
]
for raw, expected in cases:
with subtests.test(msg=f"parsing {raw!r}"):
assert parse_status(raw) == expected
注意三点:
-
subtests是fixture,必须作为参数显式声明 - 每个
with subtests.test(...)块包裹一次断言 -
msg参数会出现在测试报告里,建议带关键输入值
嵌套子测试和参数化混合用
单层subtests不够时(比如要测不同解析器 + 不同输入),可以嵌套,但更推荐和@pytest.mark.parametrize配合:
@pytest.mark.parametrize("parser_name", ["v1", "v2"])
def test_parsers_with_subtests(parser_name, subtests):
parser = get_parser(parser_name)
test_cases = [("200 OK", 200), ("302 Found", 302)]
for raw, expected in test_cases:
with subtests.test(f"{parser_name}: {raw!r}"):
assert parser.parse(raw) == expected
这样报告里会显示类似:test_parsers_with_subtests[v1-200 OK]、test_parsers_with_subtests[v2-302 Found]。比纯嵌套更清晰,也避免了subtests.test()里再套subtests.test()带来的可读性下降。
常见坑和兼容性注意
- pytest 7.0+ 原生支持subtests,但旧版本必须装pytest-subtests插件;如果用了--tb=short,子测试失败的堆栈默认只显示到with行,加--tb=long才能看到断言那一行
- 子测试里不能用pytest.skip()或pytest.xfail(),会报ScopeMismatch;要用subtests.skip()或subtests.xfail()
- subtests不支持yield风格的测试函数,只支持def函数
- 报告中子测试名长度有限制,过长会被截断,所以msg别堆太多信息,核心变量用f"{var!r}"就够了
子测试真正难的不是语法,而是判断哪些断言值得拆——不是所有for循环都要改,只有当失败时你确实需要知道“哪一组输入挂了”,才值得加subtests。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











