第 12 章:用故障矩阵验证合同
一个正常演示只能证明某条成功路径走通。网关真正的工程难点往往发生在两个动作之间:上游已经开始而客户端尚未收到内容,usage 已到达而 done 尚未到达,取消到达而结算也正在完成。测试需要把这些窗口变成可控输入。
12.1 把测试对象拆成三个层次
协议层测试解析规则,例如 UTF-8 分片、多行 data 和未完成事件。策略层测试资格与预算,例如能力不匹配时不联系上游。集成层通过真实 HTTP 连接观察响应和上游调用次数,验证异常路径没有被函数替身隐藏。
三层互相补充。只有策略函数测试,无法发现写入超时后的资源清理问题;只有端到端成功演示,又很难定位一个多行事件为何解析错误。随书实验用本地服务替身控制故障,仍然经过 HTTP、SSE 和客户端。
12.2 矩阵中的每一行要有证据
| 注入情况 | 应保持的规则 | 至少检查的证据 |
|---|---|---|
| 请求能力无候选支持 | 不启动不合格上游 | 上游接收次数为零 |
| 默认请求收到未开始拒绝 | 不自行获得切换授权 | 后备上游接收次数为零 |
| 显式授权且可信未开始拒绝 | 尝试受限、结果可解释 | 两个尝试记录与完整结果 |
| 普通 503 或发送后超时 | 不把不确定变成安全重放 | 后备次数为零、未知用量 |
| 输出部分文字后断开 | 不拼接新的生成 | 客户端部分内容、终止状态与账本 |
| usage 缺失或不符合合同 | 不确认结算 | 预留仍在,且有异常分类 |
| 另一租户请求取消 | 不影响目标运行 | 拒绝结果、原运行继续 |
| 慢消费者 | 缓冲受限且有停止路径 | 队列观测、写入超时、线程退出 |
检查一个日志里的 success 字符串,不足以证明这些规则。上游次数应来自替身的独立计数;账本应读取实际保存的状态;输出应由客户端解析。这样能减少“实现和测试重复同一个错误”的风险。
12.3 用同步条件定位窗口
并发测试若大量依赖固定 sleep,会在不同机器上变得脆弱。更好的方法是等待明确条件:替身已经接受请求、首个事件已被客户端读取、账本记录已建立,然后注入下一步动作。
等待本身也应有上限,并在失败时输出目前状态。否则一个测试会因后台线程阻塞而永久挂住,CI 看起来像网络故障,却没有定位信息。
慢消费者测试尤其要谨慎。操作系统缓冲可能先容纳一段输出,让测试误以为已经证明应用背压。实验需要足够的数据量、受限的应用队列、受控读速率和可观察停止条件,同时承认缓冲区大小与调度会影响时间。
12.4 回归报告描述行为而非排名
本书测试没有比较真实模型质量,也没有进行供应商吞吐排名。它验证确定性宿主行为和特定故障窗口。真实模型评测需要额外的数据集、任务验收与重复试验,不能把替身测试通过率写成模型可靠性。
保存运行记录时,至少带上代码版本、Python 版本、命令、测试名称、结果和失败输出。变更后重新运行受影响的测试;发布前运行整套,并实际打开生成的在线页与 EPUB。静态文档与测试各有范围,谁都不能替代另一个。
练习。 把“已经输出以后不重试”改写成一个测试规范。至少列出上游计划、客户端观察、后备调用次数和账本终态,避免仅断言某个内部函数没有被调用。