学习指南 →

· Albion Gateway College

单元测试为何单独通过、一起失败?从夹具和状态隔离排查

用原创座位登记器追踪共享列表污染,设计每次重新建立的测试夹具,解释 setUp、tearDown 和 addCleanup,并练习失败路径与测试替身边界。

测试夹具不仅是一份样例数据

单元测试需要一个明确的起点:被测对象、输入数据、临时资源,以及测试结束后的清理安排。这套准备和收尾被称为测试夹具。若起点会随前一个测试变化,同样的断言可能时而通过、时而失败,错误来源未必在被测功能本身,也可能在测试之间共享了状态。

本文用 Python 标准库 unittest 说明隔离方法。练习对象是一个简化座位登记器,初始容量为 2,登记名单为空;登记不同编号会占一个位置,满额后再登记应抛出 ValueError。编号均为虚构示例,不涉及真实预约,也不连接真实服务。

原创故障:类级别名单让两个测试互相污染

设两个测试共用同一个登记器。测试甲加入 A01 并断言剩余位置为 1;测试乙假设起点为空,先断言剩余位置为 2,再加入 B02。乙单独运行时通过,排在甲后面却失败,因为甲留下的 A01 仍在名单中。此时把乙的期望值改成 1,只是把污染写进了测试。

另一种隐蔽写法是每次创建新登记器,却把名单定义为类属性上的同一个列表。对象外壳变了,共享列表没变,仍然会相互影响。因此不能只数构造函数调用次数;要确认真正可变的成员在每个实例中独立创建,或者按需求明确共享。

每次 setUp 建立可理解的起点

在 TestCase 的 setUp 中新建登记器,并让其构造过程创建实例自己的空名单。这样每个测试方法都从容量 2、名单空的状态开始。测试甲登记 A01 后得到剩余 1;测试乙在自己的对象中仍先看到剩余 2。两个测试不需要了解对方的存在。

Python 官方 unittest 文档说明 setUp 在各测试前准备夹具。不要为了省几行代码,把易变状态移到 setUpClass 后让整个类共用;类级夹具适合经过评估的共享准备,但会提高隔离责任。真正昂贵的只读数据可以考虑复用,变更对象则要明确复制深度或重建方式。

一次测试先准备,再操作,再核对结果

设计满额测试时,先在本测试内登记 A01、A02,让剩余位置为 0;再用 assertRaises(ValueError) 核对登记 A03 被拒绝;最后检查名单仍为 A01、A02,剩余仍为 0。只确认抛出异常还不够,因为有缺陷的实现可能先把 A03 加进去,再抛错。

断言应体现对外契约,而不是过度依赖内部容器长什么样。若公开接口提供剩余数量和已登记编号,就优先通过这些接口核对。对于顺序没有保证的结果,应按集合等合适语义比较;对于顺序本来就是需求的结果,则不能为了让测试通过而随意排序。

清理资源也要覆盖准备阶段失败

当夹具使用临时目录或其他外部资源时,需要安排清理。unittest 中,setUp 成功以后,即使测试方法失败,tearDown 通常仍会运行;但如果 setUp 自己失败,tearDown 不会按正常流程执行。所以先创建资源、再做可能失败的准备时,不能只依赖 tearDown。

一种做法是在成功获得资源后立即通过 addCleanup 注册释放函数,再执行下一步准备。官方文档说明,已经注册的清理函数在 setUp 失败后仍会被调用,并按后注册先执行的次序处理。这适合有依赖关系的资源,但进程被强制终止等情况仍超出测试框架保证。

隔离还涉及时间、随机数和外部依赖

即使对象每次新建,测试仍可能读取当前日期、依赖网络响应或使用未控制的随机数。要测试“超时后拒绝登记”,可以把时钟作为可替换依赖,分别传入边界前后时刻,避免真的等待很久。要测试随机算法,则记录或控制随机来源,并解释期望性质而不是随意固定一次输出。

测试替身可帮助模拟成功、异常与超时,但不能因此断定真实服务一定兼容。单元测试验证局部逻辑,集成测试还要验证真实接口、数据格式和资源生命周期。若替身的行为与真实对象不同,局部测试全部通过也可能遗漏系统问题。两类证据应分别说明。

自测:识别三个看似方便的修补方案

方案一:强行规定测试甲永远最后运行,避免污染别人。答案:这隐藏了依赖,并未修复隔离。方案二:测试乙开始时把共享名单清空。答案:可以暂时恢复状态,却仍容易受并发和其他共享字段影响;通常优先让各测试获得独立对象。若共享本身是被测需求,则应把相关过程明确放进一个场景。

方案三:setUp 创建临时目录后,在读配置时抛错;目录清理只在 tearDown 中。答案:存在清理缺口,应在成功创建目录后就注册清理。补充题:只断言满额登记抛出异常,缺少什么?答案:还要检查失败操作没有意外改变名单和剩余数量。

用重复运行证明隔离,但别把它当正确性证明

修复后应分别运行失败测试、相关测试组以及完整测试集,条件允许时改变执行次序,观察是否仍有依赖。重复稳定通过能提高对隔离的信心,但不是程序没有缺陷的证明。覆盖率也只说明哪些代码被执行过,不能证明断言充分、需求正确或所有边界已经考虑。

复习时给每个测试写一句目的、一份起始状态、一个主要操作和可观察结果,再列出它读写的外部状态。如果一个测试必须依赖另一个测试留下的数据,考虑把它们合并为明确的流程测试,或重新设计夹具。测试应帮助定位行为偏差,而不是靠顺序和偶然环境维持绿色结果。

参考资料

下一步

如想练习单元测试设计,请说明编程模块、测试框架及单独运行和整组运行时的不同表现。相关辅导能否安排,取决于学科匹配和导师时间。

继续阅读

学院在线咨询

欢迎咨询

你好,欢迎咨询!你是学生、招生代理、求职者,还是想申请成为导师?请告诉我你的咨询方向。

[email protected]