Signal Desk
返回Codex 教程

用证据诊断、浏览器验收和安全发布1 / 3

官方方法 · 进阶实验动手教程阅读约 10 分钟 · 实操约 25 分钟

从失败测试追到根因,而不是罗列猜测

运行一个可复现的折扣计算 bug,建立执行链、对照实验和最小修复面。

先知道终点

做完你会得到
一份区分症状、根因、触发条件和修复证据的 diagnosis.md
开始前只需要
能在终端运行 node;理解只读诊断与实施修复是不同授权
最后留下这些证据
修复前测试稳定失败;根因指向具体代码路径;修复后回归测试通过

内容校准于 2026-07-30 · 第 23 / 38 节已发布课程

跟着材料做,不只阅读

本节练习资料

建议先做,再看答案

差一分钱,能猜出十个原因

折扣测试失败了。有人怀疑浮点数,有人怀疑缓存,还有人顺手把时区也列进来。可能性越来越完整,代码却一行没靠近根因。

诊断最容易伪装成工作量。

真正的交付物不是猜测清单,而是一条能被反驳的因果链。 这节课会运行一个失败的折扣计算器,把输入、执行路径、对照实验和最小修复放进同一份假设账本。

诊断的交付物是因果证据

“可能是缓存、网络或数据问题”不是诊断。合格诊断要回答:

  • 症状怎样稳定复现;
  • 请求或数据经过了哪些真实代码路径;
  • 哪个对照实验排除了其他解释;
  • 根因、触发条件和放大因素分别是什么;
  • 最小修复为什么能阻断这条失败链。

本节使用可运行的折扣计算器,不靠假想案例。

第一步:建立修复前基线

下载两个 .mjs 文件放到同一空目录:

debugging-lab/
  coupon-calculator.mjs
  coupon-calculator.test.mjs

运行:

node --test coupon-calculator.test.mjs

预期:三项测试中有一项失败,“SAVE10”在 100 元订单上没有得到 90。把完整命令、退出码和失败断言保存到 diagnosis.md 的“症状”部分。

如果测试没有失败,先检查文件是否下载完整、名称是否一致、Node 版本是否支持 node:test。基线与教程不一致时不要继续,这正是成熟教程必须提供的失败分支。

第二步:先让 Codex 只读追执行链

目标
- 只读诊断 coupon-calculator.test.mjs 中的失败,不修改文件。

请按顺序
1. 运行失败测试并保留原始断言;
2. 从测试输入追到 calculateTotal 的每个判断;
3. 列出最多三个与当前证据一致的假设;
4. 为每个假设设计一个最小对照实验;
5. 区分症状、根因、触发条件和放大因素。

边界
- 不修改代码,不放宽断言,不增加依赖。
- 每个结论引用文件和具体表达式。

你应该看到什么

执行链应包含:测试构造订单 → 调用 calculateTotal → 标准化 coupon → 判断是否应用折扣 → 计算 total。若回答跳过中间值,只说“折扣逻辑有 bug”,要求它打印或推导具体变量。

第三步:手工做三个对照

在不改文件的前提下,让 Codex 或你自己运行:

node -e "import('./coupon-calculator.mjs').then(({calculateTotal}) => console.log(calculateTotal({subtotal:100,coupon:'SAVE10'})))"
node -e "import('./coupon-calculator.mjs').then(({calculateTotal}) => console.log(calculateTotal({subtotal:100,coupon:'save10'})))"
node -e "import('./coupon-calculator.mjs').then(({calculateTotal}) => console.log(calculateTotal({subtotal:100,coupon:' NONE '})))"

比较大写、已经小写和带空格无券三组。对照的目的不是收集更多输出,而是改变一个变量,观察失败是否随之变化。

如果小写 save10 生效而大写 SAVE10 不生效,问题就从“所有折扣计算错误”缩小到“输入标准化与比较约定不一致”。

第四步:写清四层因果

参考结构:

症状:SAVE10 用例期望 90,实际 100。
根因:coupon 被转换成小写后,却与大写常量 SAVE10 比较。
触发条件:调用方传入任意大小写形式的 SAVE10。
放大因素:当前只有一个失败用例,错误提示没有显示标准化后的值。

根因必须是能解释对照结果的具体机制。触发条件不是根因;“用户输入大写”本身没有错,除非契约明确只允许小写。

第五步:提出最小修复与反例

两个候选方案:

  1. 标准化为小写,并与 "save10" 比较;
  2. 不标准化,只接受 "SAVE10"。

检查现有测试与函数语义后,选择能保持大小写不敏感契约的方案。再问:它会不会误把 " SAVE10 "、空值或未知券码当成有效?

允许 Codex修改前,写清:

只修改 coupon-calculator.mjs 中券码比较,使现有大小写不敏感契约成立。
不要修改测试来迁就实现,不改变无券和未知券码行为。
修复后运行完整测试,并报告 diff。

第六步:验证修复没有只迎合一个样本

至少验证:

  • 原失败测试从失败变为通过;
  • 其他两项测试仍通过;
  • 代码 diff 只包含必要比较变化;
  • 对照命令的大写与小写结果一致;
  • 未知券码仍返回原价。

把修复前后测试摘要都保留。只有“修复后绿灯”无法证明问题曾被正确复现。

第七步:对照参考诊断

打开答案文件,逐项比较:

  • 是否有原始失败证据;
  • 根因是否定位到同一个条件表达式;
  • 是否把输入大写误写成用户错误;
  • 修复是否改变了测试或其他行为;
  • 是否说明未覆盖的边界。

常见失败

  • Codex 直接修改:你没有把“只读诊断”作为任务边界。
  • 它列出十个假设:要求每个假设必须对应当前证据和可执行对照。
  • 它删除失败测试:这是把报警器关掉,不是修复。
  • 它顺手重构函数:最小修复面不清楚,增加了回归范围。

让下一个实验淘汰一个解释

挑一个可复现 bug,只保留当前最能解释证据的两项假设。为每项写一个结果不同的对照实验。

实验没有区分力,就不值得运行。每轮诊断都应该让可能世界变少,而不是让报告变长。

完成检查

  • 我保留了修复前的失败输出
  • 执行链包含关键中间值和判断
  • 对照实验一次只改变一个变量
  • 根因、触发条件和放大因素已分开
  • 修复后完整回归与 diff 都经过检查

参考与校准来源

本文是官方资料的中文转译与教学重组,不是逐字翻译;产品能力、命令、默认值与安全边界以下列官方原文为准。