subtests不能用外部assert,因为assert必须置于with subtests.test()缩进块内才能被上下文捕获为独立子测试结果;块外assert会直接中断整个测试函数。

subtests 为什么不能用 assert 断言?
直接在 with subtests.test() 块里写 assert 是无效的——它不会被识别为子测试失败,而是直接抛出异常中断整个测试函数。pytest-subtests 的核心机制是靠 subtests.test() 上下文管理器捕获异常并记录为独立子测试结果,不是靠断言本身驱动。
- 错误写法:
assert x == y在with subtests.test()内部会终止当前子测试,并跳过后续子测试 - 正确做法:所有断言必须放在
with subtests.test()的缩进块内,且由该上下文捕获异常 - 本质是“把断言包裹进子测试生命周期”,不是“在子测试里随便断言”
如何给每个子测试加可读的 name 和 params?
subtests.test() 的 name 参数决定报告中显示的子测试标识,**kwargs 会作为参数透传到上下文块中,常用于构造清晰的失败信息。
-
name推荐用 f-string 动态生成,比如f"input={val}",否则多个子测试在 pytest -v 输出里全显示为同一个名字 - 避免只用序号命名(如
name=f"case-{i}"),调试时无法快速定位数据问题 -
params不是内置参数,但你可以传任意关键字参数,在块内直接使用:with subtests.test(name=f"len={n}", n=n):,然后块内直接用n
示例:
def test_parse_json(subtests):
cases = [("{}", {}), ('{"a":1}', {"a": 1}), ("invalid", None)]
for i, (inp, expect) in enumerate(cases):
with subtests.test(name=f"case-{i} input={repr(inp)}"):
result = try_parse_json(inp)
assert result == expect
子测试失败后,其他子测试还会执行吗?
会。这是 subtests 最关键的价值:单个子测试失败不中断整个测试函数,其余子测试照常运行。
- 适合批量验证多组输入(如配置项组合、边界值、不同编码格式)
- 注意:如果前置逻辑(比如 setup 或循环外代码)出错,整个测试函数仍会失败
- pytest 报告里每个子测试单独计数,失败的标为
f,成功的标为.,汇总行会显示类似1 failed, 5 passed, 2 subtests - 用
pytest --tb=short可看清每个子测试的失败堆栈,但 traceback 默认只显示到with subtests.test()行,不是最内层断言行
和参数化(@pytest.mark.parametrize)有什么区别?
两者都能跑多组数据,但语义和控制粒度不同:参数化是“多个独立测试函数实例”,subtests 是“一个测试函数内的多个原子检查点”。
- 参数化每个 case 是独立的测试项,可单独运行(
pytest test.py::test_func[case-0]),subtests 不可单独触发 - subtests 共享 setup/teardown,适合需要复用初始化状态的场景(如共用一个数据库连接、临时文件目录)
- 参数化更利于并行(pytest-xdist),subtests 天然串行执行
- 调试时,参数化失败直接停在对应 case;subtests 需要结合
name定位具体分支
别为了“看起来像参数化”硬套 subtests——如果不需要共享状态、也不需要强制所有 case 跑完,优先用 @pytest.mark.parametrize。
真正需要 subtests 的典型场景:校验一个函数对多种异常输入的响应是否符合预期(比如空输入、超长输入、非法字符),且这些输入需要在同一上下文(如 mock 已 patch 的模块)中验证。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











