Python unittest subTest:一组数据断言失败,后面的样本怎样继续检查
先看失败以后还访问了哪些样本
把一批输入放进同一个测试方法的循环里,可以减少重复代码,但普通断言一旦失败,当前方法就会停止。报告只显示第一个问题,后面几组甚至没有执行。subTest 能为每次迭代建立可识别的结果边界,让常规失败被记录后继续下一组,同时仍然保留整个测试未通过的事实。
下面使用 Python 3.12.14 的标准库,只在内存里建立测试对象。我们准备长度分别为一、二、三的字符串,却故意把第二组期望长度写成九。这个错误是演示条件,不是待发布功能的通过结果。保存为 demo.py,运行 python demo.py;外层脚本检验的是测试框架是否如预期记录这次失败。
把相同数据交给两种循环
Plain 在普通循环里断言,Grouped 则把断言放进 self.subTest,并附带当前文本标签。两者都记录实际见过的样本,避免从报告条数猜测控制流。例子没有启用 failfast,也没有并行执行,观察对象是同一个测试方法内的串行迭代。
执行时显式创建 TestResult,分别接收两个测试方法的结果。外层断言要求二者都有一次失败、没有其他错误,并且 wasSuccessful 都为假。这样既能验证继续执行的效果,又不会把“实验脚本正常结束”误读成“故意出错的样本全部通过”。
AI概念示意图:三张样本卡依次经过检查,第二张留下红色记录,第三张仍能到达检查点。图片只说明概念,不是运行截图。
import unittest
DATA = [("A", 1), ("BB", 9), ("CCC", 3)]
class Plain(unittest.TestCase):
def setUp(self):
self.seen = []
def test_lengths(self):
for text, expected in DATA:
self.seen.append(text)
self.assertEqual(len(text), expected)
class Grouped(Plain):
def test_lengths(self):
for text, expected in DATA:
with self.subTest(text=text):
self.seen.append(text)
self.assertEqual(len(text), expected)
for cls, expected_seen in [(Plain, ["A", "BB"]),
(Grouped, ["A", "BB", "CCC"])]:
case = cls("test_lengths")
result = unittest.TestResult()
case.run(result)
assert case.seen == expected_seen
assert result.testsRun == 1
assert len(result.failures) == 1
assert len(result.errors) == 0
assert not result.wasSuccessful()
print(cls.__name__ + ":", case.seen,
"tests=", result.testsRun,
"failures=", len(result.failures),
"success=", result.wasSuccessful())
if cls is Grouped:
assert "text='BB'" in str(result.failures[0][0])
print("demo assertions: passed; both inner test runs failed as expected")继续执行不等于忽略失败
Plain 一行的 seen 内容为 A、BB,Grouped 一行则为 A、BB、CCC,两行都会显示 tests= 1 failures= 1 success= False。区别是第三组有没有被检查,不是第二组是否被放行。最后一行明确说明外层演示断言通过,而两次内部测试运行都按预期失败,不能拿这行文字作为业务测试全绿的证据。
testsRun 仍为一,因为这里每次运行的是一个测试方法,三个子测试没有变成三个独立测试方法。失败记录关联的对象包含 text=BB 标签,便于定位数据组。为真实数据集选择标签时,应使用简短且能识别样本的内容,避免把大量原始输入或敏感信息塞进报错报告。
这份演示直接检查结果对象,便于输出稳定的验收信息。项目里通常交给标准文本运行器显示堆栈和汇总;若自行包装运行流程,也要检查最终成功标记,不能仅以测试方法返回了控制权判断成功。
子测试不是全套独立环境
setUp 和 tearDown 围绕测试方法执行,不会为每个 subTest 自动重建一份环境。若某组修改了列表、数据库或共享对象,下一组可能继承这次改动;需要在循环内显式建立新样本,或者拆成独立测试方法。子测试提供记录边界,并不自动提供事务回滚、并行调度或状态隔离。
另外,断言失败以后,失败所在 with 块余下的语句仍不会执行,继续的是退出该块后的控制流;开启快速失败选项也会改变本例继续迭代的效果。实际使用时,把一组相关断言放在块内,清理放到可靠的位置,并让命令行测试运行器根据最终结果返回失败状态。能一次看见更多失败,应服务于更快修复,而不是把失败藏起来。


