Test-Driven Development(TDD)是一个内部技能,属于 romiluz13/cc10x 技能集,被标注为 Internal skill,并提示使用 cc10x-router 处理所有开发任务。
用途
该技能用于指导开发过程中的测试驱动开发实践。其概述指出:先写测试,看着它失败,再写最少量的代码使其通过。核心原则是:如果你没有亲眼看到测试失败,你就不知道它是否测试了正确的东西。文档强调,违反规则的文字就是违反规则的精神。
适用时机
该技能应当始终用于新功能、缺陷修复、重构和行为变更。例外情况包括一次性原型、生成的代码和配置文件,但文档要求在这些例外上“询问你的人类伙伴”。文档明确警告:如果出现“就这一次跳过 TDD”的想法,应停止,因为那是合理化。
铁律
文档规定的铁律是:没有先失败的测试,就没有生产代码。如果先写了代码再补测试,必须删除代码并从头开始,不允许保留为“参考”、不允许在写测试时“改编”、不允许查看它。删除就是删除,必须从测试重新实现。
测试过程纪律
文档指出测试运行器(Vitest、Jest)默认进入 watch 模式,会导致进程无限挂起。强制规则包括:始终使用 run 模式,永远不要调用 watch 模式。具体命令示例:Vitest 使用 npx vitest run(而不是 npx vitest);Jest 使用 CI=true npx jest 或 npx jest --watchAll=false;npm scripts 使用 CI=true npm test 或 npm test -- --run。文档建议在所有测试命令前加 CI=true。TDD 循环完成后,应验证没有孤立进程:pgrep -f "vitest|jest" || echo "Clean",若发现则使用 pkill -f "vitest" 2>/dev/null || true 终止。
Red-Green-Refactor 与垂直切片
文档以图示呈现 RED(失败)-> GREEN(通过)-> REFACTOR(清理)-> 下一个功能的循环。它强调垂直切片:错误做法是先写所有测试(test1 到 test5)再写所有实现(impl1 到 impl5);正确做法是一个测试接一个实现,每次循环接收上一轮学到的信息。文档解释,批量先写测试会产生坏测试:测试的是想象的行为而非实际行为、测试形状而非面向用户的行为、对真实变化不敏感、在理解实现前就锁定测试结构。
RED 阶段
编写一个最小测试展示应当发生什么。文档给出好例子(测试失败操作重试 3 次,检查 result 和 attempts)和坏例子(测试 mock 而非真实代码,名称模糊)。要求:一个行为、清晰名称、真实代码(除非不可避免不使用 mock)。验证 RED 是强制的:必须使用 CI=true npm test 运行,确认测试失败(不是报错)、失败信息符合预期、失败是因为功能缺失而非拼写错误。如果测试通过,说明测的是已有行为,应修正测试;如果测试报错,应修复错误直到正确失败。
GREEN 阶段
编写最简单代码使测试通过。好例子只写足够通过的逻辑;坏例子过度设计(加入 maxRetries、backoff、onRetry 等 YAGNI 内容)。文档强调不要添加功能、重构其他代码或超出测试“改进”,不要硬编码测试值,要实现适用于所有输入的通用逻辑。验证 GREEN 是强制的,需确认测试通过、其他测试仍通过、输出干净(无错误警告)。
REFACTOR 阶段
仅在绿灯后进行:移除重复、改进名称、提取辅助函数、保持测试绿灯、不添加行为。然后重复,为下一个功能写下一个失败测试。
好测试与行为聚焦
文档以表格对比质量好坏:最小(一个测试只做一件事,名称含“and”就拆分)、清晰(名称描述行为)、展示意图(演示期望 API)。它要求测试对象如何协作而非其内部内容;如果测试检查 .state、.length 或私有字段,就是在测试结构,会在内部改变而行为未变时失败。文档给出正确与错误示例:函数输出应断言返回值而非内部缓存;组件行为应断言屏幕文本而非 state 字段;服务交互应断言响应状态而非调用次数。每个断言应回答“用户/调用者观察到了什么”,而非“内部发生了什么”。
跨代理的测试契约
当 CC10x 跨多个代理路由工作时(规划者写测试规格、构建者实现、审查者验证),测试文件本身就是契约。规划者以测试名称和断言定义期望行为;构建者先写测试再实现到绿灯;审查者重新运行同样的测试。不要在文字中重复契约;如果测试文件表达了需求,测试文件就是需求。
输出格式
文档规定输出格式为:TDD Cycle 下分 Requirements、RED Phase(测试名、命令、结果、失败原因)、GREEN Phase(实现摘要、文件路径行号、命令、结果)、REFACTOR Phase(改进内容、命令、结果)。
集成与实况证明
当任务依赖真实 API 调用、种子或可重置数据、浏览器或 worker 编排、跨服务副作用、负载或压力行为时,单元测试不够。此时应阅读 references/integration-and-live-proof.md,保持 TDD 用于内循环,并为外部证明提升验证深度。
覆盖率阈值与测试优先级
项目默认目标为 80%+ 代码覆盖率,覆盖分支、函数、行、语句各 80%,使用 npm run test:coverage 或等效命令验证。低于阈值应补充缺失测试。80% 覆盖率意味着对先测试什么做出审慎选择,聚焦关键用户路径(认证、支付、数据完整性)、多分支复杂逻辑、曾经出错的代码。不要因为代码“看起来简单”而跳过测试,80% 是下限而非上限。
遇到困难与可测试性设计
文档列出常见问题与解决方案:不知如何测试,就写期望的 API、先写断言、询问人类伙伴;测试太复杂,说明设计太复杂,应简化接口;必须模拟一切,说明代码耦合太紧,应使用依赖注入;测试设置庞大,应提取辅助函数,仍然复杂就简化设计。可测试性设计要点:接受依赖而非创建依赖(processOrder(order, paymentGateway) 可测试,内部 new StripeGateway() 难测试);返回结果而非产生副作用;小表面积。
红旗与合理化预防
文档列出应停止并重来的红旗:测试前写代码、实现后写测试、测试立即通过、无法解释测试为何失败、测试“稍后”添加、合理化“就这一次”、声称已手动测试、声称测试后补达到同样目的、声称关乎精神而非仪式、保留参考或改编现有代码、已花 X 小时删除浪费、声称 TDD 教条自己务实、声称这不一样。文档用表格逐一反驳这些借口,例如“太简单不用测”对应“简单代码也会坏,测试只需 30 秒”;“删除 X 小时浪费”对应“沉没成本谬误,保留未验证代码是技术债”。
示例:缺陷修复
文档以空邮箱被接受为例:RED 写测试拒绝空邮箱,验证失败得到 expected 'Email required', got undefined;GREEN 在 submitForm 中检查 data.email?.trim() 并返回错误;REFACTOR 如有需要提取多字段验证。
验证清单
标记工作完成前需检查:每个新函数/方法都有测试;每个测试在实现前都看到失败;每个测试因预期原因失败;为每个测试写了最少代码;所有测试通过;输出干净;测试使用真实代码(mock 仅在不可避免时);覆盖边缘情况和错误;没有挂起的测试进程(pgrep -f "vitest|jest" 返回空)。无法勾选全部则表示跳过了 TDD,应重新开始。
最终规则
生产代码 → 测试存在并且先失败;否则 → 不是 TDD。未经人类伙伴许可,没有例外。