TT Lab
开始
学习 学习路径 课程

测试工具实战

隔离测试失败路径与副作用

在 TT Lab 中继续学习

目标

在没有网络、也没有真实等待的情况下,重现调用次数、退避、文件保留和保存失败。

为什么重要

一次正常请求成功,并不能保证边界值和故障恢复也没有问题。本实验先为每个函数的契约编写小型实现或测试,再通过真实运行把它们串起来。评分不会只看代码是否存在或报告中的措辞,而是检查结果、异常和保存的状态。请保留前面步骤的代码,继续进行下一步。

步骤

  1. 在 /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
  1. 在 /root/work/test-failures-lab/test_service.py 中把 send 配置为:第一次抛出 TimeoutError,第二次返回 'fresh',然后确认 fetch 返回 'fresh'。
  2. 在 /root/work/test-failures-lab/test_service.py 中传入一直抛出 TimeoutError 的 send,确认总共调用 3 次后,TimeoutError 被向上传播。
  3. 在 /root/work/test-failures-lab/test_service.py 中测试:抛出 ValueError 的 send 恰好只被调用 1 次,并且同一类型的异常被向上传播。
  4. 在 /root/work/test-failures-lab/test_service.py 中检查:当所有 send 都抛出 TimeoutError 时,传给 sleep 的值恰好是 [0.1, 0.2]。不会真的进入睡眠。
  5. 在 /root/work/test-failures-lab/test_service.py 中用韩文字符串让调用成功,然后以 UTF-8 读取 cache 文件,确认保存的内容与之相同。
  6. 在 /root/work/test-failures-lab/test_service.py 中先向 cache 写入 'old',再让 send 持续失败。确认异常发生后文件内容仍然是 'old'。
  7. 在 /root/work/test-failures-lab/test_service.py 中测试:即使 send 成功,只要 cache 写入失败,就会传播 OSError。把 tmp_path 目录本身作为 cache 传入,就能确定性地让写入失败。

参考

抓住成功路径上多余的调用

在 /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 直接复现。保存文件后再运行一次。