TDD — 测试先行执行
用途
该技能用于通过红-绿-重构循环实现行为。当请求测试先行执行,或编码工作流选择该方式时应用。它不派发 worker;如需 worker 派发,使用 shared/references/model-preview.md 及其唯一获批的路由快照。嵌套技能与 runner 直接消费它,无需再次选择或 worker。
铁律
在修复所请求的行为之前,先建立一个失败的行为用例。当捕获到的失败能证明同一代码上的同一缺陷时,可复用该失败。
保留已有的有效工作。不要仅仅为了改变历史顺序而删除并重建代码。
一个通过的新测试可以刻画既有行为,但不能证明缺失的行为,也不能证明该测试是错的。使用相关的失败或受控反例来确定它检测的是什么。
第 0 步 — 这是谁的代码?
对未测试遗留代码进行有风险的改动时,应用 safe-incremental-coding 来补充缺失的特征化。复用覆盖改动面的既有行为证据与失败证据。若该保护已存在,则直接进行。
循环
每个行为增量运行一次循环。每次通过都保持足够小,小到丢弃也不心疼。
框定最小的可逆动作。一个调用方可见的行为增量——最廉价的可逆学习步骤,比感觉自然的粒度更小。如果你无法用一句话陈述结果,说明这一步太大,需拆分。在选择时说出:“这里最小的可逆动作是……”。
先写测试,预测失败,看着它失败。从调用方视角表达代码应该做什么。运行前说出预期失败——不同的失败已经教会了你一些东西。
用最简单的方式达到绿灯。用能通过的最少代码;朴素即可。这是战术步骤,不是设计步骤——此时不要打磨。
在绿灯下重构。改进一个具体的设计问题,然后在下一个行为变更前运行受影响的测试。当存在坏味道时,应用 clean-code 来命名它。在必须成立的地方断言内部不变量:断言是可执行的文档,其失败是 bug,而不是要捕获并忽略的条件。
整合连贯的增量。保持每个增量小而通过。仅在用户或调用方授权时提交;用已完成的行为和通过的检查来选择边界,而不是用计时器。
控制变量
只有能归因某一步的效果,才能从中学到东西:
一次只改一件事。绝不把重构与行为变更、配置调整捆绑在一起。
间歇性失败的测试就是失败,绝不是可以重跑通过的。
避免靠巧合编程。如果代码通过,要知道为什么。
绝不糊弄检查。为达到绿灯而删除、跳过、弱化、收窄或 mock 掉测试是被禁止的;在修复权限内纠正已被证实的测试实现错误。对已接受产品契约的变更需要决策(shared/references/engineering-rules.md,Contract integrity)。
写测试的标准
写时规则。当既有测试的价值存疑时,test-lens 是裁判;以下是你要遵循的标准,以免被它判定为不合格。
风格排序:基于输出(断言纯函数的返回值)> 基于状态 > 基于通信(mock)。通过函数式核心(纯决策)包裹可变外壳(薄 I/O 胶水)把代码推向基于输出。
断言可观察行为,绝不断言实现细节。绝不断言对 stub 的调用。
AAA——Arrange / Act / Assert,各一个——以领域行为语句命名:delivery_with_a_past_date_is_invalid,而不是 testIsValid_case3。
难以编写的测试是设计坏味道——修设计,而不是修测试。
重构试金石:如果实现被换成完全不同的一个,这个测试是否仍然有效?它应该有效。
设计与风险提示
在选择测试和重构时使用这些提示;它们不要求每次整合前再做一次评审。
Farley 的五个杠杆:模块化、内聚、关注点分离、抽象、低耦合。在改动范围内处理这些属性的回归。
“如果……会怎样?”:选择该行为暴露的失败用例,例如无效输入、依赖失败、并发、安全或金钱损失。复用验收契约中已有的用例与决策。
每步输出契约
每次循环迭代(或连贯批次)后报告:
changed:编辑了什么。
behavior:代码现在做了什么之前没做的事,或行为被保留。
risk_guarded:这一步的测试现在守护的主要设计或数据风险。
verification:运行的测试或检查,或为何无法运行。
路由
存储状态、队列、重试、迁移、外部 API → 复用已获批的 data-systems-coding-lens 发现。仅对未覆盖的数据风险或变更的设计面运行该 lens,而不是在每次循环迭代前运行。
在重构步骤中应用 clean-code 获取坏味道词汇与命名。
在判断既有测试是否值得保留时应用 test-lens。
当循环中途出现无法解释的失败时应用 diagnose。已理解的红色或回归留在当前循环中;使用既有证据,而不是启动第二次调查。
常见陷阱
巨型步骤——在首次运行测试前写了半天的代码。
事后补测——为既有代码量身定做的测试可能错过预期行为。对照验收契约和相关反例检查它;保留有效工作。
追逐覆盖率——覆盖率是循环的副作用,绝不是目标。
绿灯下镀金——在不安全时做设计;设计属于重构步骤。
“没时间测试”——不存在速度与质量的权衡;更快的方法就是保持低返工。
不要扩大范围去清理无关代码。
不要因为听起来整洁就添加层、服务或辅助工具。
当补救带来的间接性多于清晰度时,不要弱化无害的局部耦合。
当设计需要转向时,以小步重构在绿灯到绿灯之间调整——改变主意是有代价的;这笔账正是小步早先为你买来的速度。
不要引用或重建本技能所提炼书籍的原文。