隔离测试失败路径与副作用
目标
在没有网络、也没有真实等待的情况下,重现调用次数、退避、文件保留和保存失败。
为什么重要
一次正常请求成功,并不能保证边界值和故障恢复也没有问题。本实验先为每个函数的契约编写小型实现或测试,再通过真实运行把它们串起来。评分不会只看代码是否存在或报告中的措辞,而是检查结果、异常和保存的状态。请保留前面步骤的代码,继续进行下一步。
步骤
- 在
/root/work/test-failures-lab/test_service.py中测试 fetch 是否原样返回 send 给出的字符串,并且恰好只调用 send 一次。cache 要作为 tmp_path 下的 pathlib.Path 传入。第一次准备使用下面的命令。
mkdir -p /root/work/test-failures-lab
cp /opt/fixtures/practice_depth/test-failures-lab/* /root/work/test-failures-lab/
cd /root/work/test-failures-lab
- 在
/root/work/test-failures-lab/test_service.py中把 send 配置为:第一次抛出 TimeoutError,第二次返回 'fresh',然后确认 fetch 返回 'fresh'。 - 在
/root/work/test-failures-lab/test_service.py中传入一直抛出 TimeoutError 的 send,确认总共调用 3 次后,TimeoutError 被向上传播。 - 在
/root/work/test-failures-lab/test_service.py中测试:抛出 ValueError 的 send 恰好只被调用 1 次,并且同一类型的异常被向上传播。 - 在
/root/work/test-failures-lab/test_service.py中检查:当所有 send 都抛出 TimeoutError 时,传给 sleep 的值恰好是 [0.1, 0.2]。不会真的进入睡眠。 - 在
/root/work/test-failures-lab/test_service.py中用韩文字符串让调用成功,然后以 UTF-8 读取 cache 文件,确认保存的内容与之相同。 - 在
/root/work/test-failures-lab/test_service.py中先向 cache 写入 'old',再让 send 持续失败。确认异常发生后文件内容仍然是 'old'。 - 在
/root/work/test-failures-lab/test_service.py中测试:即使 send 成功,只要 cache 写入失败,就会传播 OSError。把 tmp_path 目录本身作为 cache 传入,就能确定性地让写入失败。
参考
- 软件包已安装在镜像中,不需要联网,也不需要 pip install。
- 准备阶段的复制只做一次。再次复制会使工作文件恢复初始状态。
/root/work/test-failures-lab/service.py是第一次准备时复制过来的、用于本地运行的提供代码。不要修改它,也不要用示例代码覆盖它,只编写test_service.py。- 测试要用
from service import ...导入提供的函数。评分会把测试单独复制到另一个临时目录,在正确实现和有缺陷的实现上运行。每一步都必须在实际运行测试时抓住对应的缺陷,而在正确代码上,已运行的测试必须全部通过。收集错误、导入错误和被强制终止都不算检出。不要依赖外部文件或网络。 - 每一步的评分都在 45 秒内执行。不要写出无限循环或真实等待。
抓住成功路径上多余的调用
在 /root/work/test-failures-lab/test_service.py 中测试 fetch 是否原样返回 send 给出的字符串,并且恰好只调用 send 一次。cache 要作为 tmp_path 下的 pathlib.Path 传入。第一次准备使用下面的命令。
mkdir -p /root/work/test-failures-lab
cp /opt/fixtures/practice_depth/test-failures-lab/* /root/work/test-failures-lab/
cd /root/work/test-failures-lab
注入一个会把调用记录到 calls 列表里的函数。只看返回值,就会漏掉重复调用。
评分可以用 bash /opt/lab/checks/test-failures-lab/01-contract.sh 直接复现。保存文件后再运行一次。
失败一次后恢复
在 /root/work/test-failures-lab/test_service.py 中把 send 配置为:第一次抛出 TimeoutError,第二次返回 'fresh',然后确认 fetch 返回 'fresh'。
用 iterator 或调用次数来控制顺序。测试本身不要连接真实网络。
评分可以用 bash /opt/lab/checks/test-failures-lab/02-contract.sh 直接复现。保存文件后再运行一次。
检查总尝试次数和最后一个异常
在 /root/work/test-failures-lab/test_service.py 中传入一直抛出 TimeoutError 的 send,确认总共调用 3 次后,TimeoutError 被向上传播。
重试 3 次与总共尝试 3 次并不相同。本契约规定的是包含最初那次请求在内共 3 次。
评分可以用 bash /opt/lab/checks/test-failures-lab/03-contract.sh 直接复现。保存文件后再运行一次。
阻止对永久性错误的重试
在 /root/work/test-failures-lab/test_service.py 中测试:抛出 ValueError 的 send 恰好只被调用 1 次,并且同一类型的异常被向上传播。
不能只检查异常类型,还要检查 calls 的长度,才能抓住毫无意义的两次额外调用。
评分可以用 bash /opt/lab/checks/test-failures-lab/04-contract.sh 直接复现。保存文件后再运行一次。
确认退避序列和最后一次等待
在 /root/work/test-failures-lab/test_service.py 中检查:当所有 send 都抛出 TimeoutError 时,传给 sleep 的值恰好是 [0.1, 0.2]。不会真的进入睡眠。
把 delays.append 作为 sleep 参数传入。最后一次失败之后没有下一次尝试,所以也不应该再等待。
评分可以用 bash /opt/lab/checks/test-failures-lab/05-contract.sh 直接复现。保存文件后再运行一次。
同时确认返回值和磁盘内容
在 /root/work/test-failures-lab/test_service.py 中用韩文字符串让调用成功,然后以 UTF-8 读取 cache 文件,确认保存的内容与之相同。
只返回成功结果却漏掉文件写入的实现,仅靠返回值测试是抓不住的。
评分可以用 bash /opt/lab/checks/test-failures-lab/06-contract.sh 直接复现。保存文件后再运行一次。
不让网络失败清掉已有的缓存
在 /root/work/test-failures-lab/test_service.py 中先向 cache 写入 'old',再让 send 持续失败。确认异常发生后文件内容仍然是 'old'。
按“准备 → 执行并检查异常 → 确认文件状态”的顺序来构造。不要只看文件是否存在。
评分可以用 bash /opt/lab/checks/test-failures-lab/07-contract.sh 直接复现。保存文件后再运行一次。
不把保存失败伪装成成功
在 /root/work/test-failures-lab/test_service.py 中测试:即使 send 成功,只要 cache 写入失败,就会传播 OSError。把 tmp_path 目录本身作为 cache 传入,就能确定性地让写入失败。
要制造不依赖权限变更的错误。这与只确认返回成功字符串的测试,承担的是不同的职责。
评分可以用 bash /opt/lab/checks/test-failures-lab/08-contract.sh 直接复现。保存文件后再运行一次。