用途
common-test-driven-development 是一个面向 AI 编程智能体的技能,用于在创建或修改代码时强制采用测试驱动开发(TDD)方式:先写单元测试,再写实现,让测试驱动实现,从而保证正确性并防止回归。
何时使用
技能适用于以下场景:
- 创建新的函数、类、方法或模块
- 修改已有的业务逻辑或行为
- 修复缺陷(先写一个能复现该缺陷的失败测试)
- 重构代码(确保改动前后测试都存在且通过)
- 用户要求实现任何功能或逻辑
主要流程
1. 先检查已有测试
在编写任何实现代码之前,先在代码库中搜索与即将修改的代码相关的已有测试文件。查找匹配 .test.、.spec.、_test.、test_. 等模式的文件,或 tests/、tests/ 目录。
如果找到已有测试,先运行它们。如果通过,则保留它们——不要修改通过的测试,除非断言因有意的行为变更而不再有效。如果已有测试失败,且其断言反映的是与新需求冲突的过时行为,只更新那些具体的断言,绝不整体删除或重写测试。
2. 先写一个简单的失败测试
在实现任何逻辑之前:
- 创建描述预期行为的最小单元测试。
- 该测试必须可运行,并且必须失败(红阶段),因为实现尚不存在或不满足新需求。
- 测试命名要清晰描述所验证的行为(例如 it should return the sum of two numbers 或 test_empty_list_returns_none)。
- 不要一次写多个测试——从一个开始。
3. 用最小实现让测试通过
只编写足以让失败测试通过的生产代码(绿阶段)。不要添加测试尚未要求的功能,不要在此阶段过度设计或预判未来需求。运行测试确认其通过。
4. 在测试保持通过的前提下重构
测试通过后,在不改变行为的前提下改进代码结构、命名或消除重复。重构后运行所有相关测试,确认没有破坏任何东西。
5. 针对更多行为重复
对每一段新行为:写一个新的失败测试,用最小改动让它通过,必要时重构,直到完整需求被满足。
测试质量规则
- 每个测试必须只验证一个行为或场景。
- 测试必须相互独立——不共享可变状态,不依赖执行顺序。
- 使用可读作规格说明的描述性测试名。
- 优先测试公共接口,而非内部实现细节。
- 避免什么都 mock——只 mock 外部依赖(I/O、网络、数据库、时钟)。
- 保持测试快速——单元测试不得依赖网络、文件系统或数据库。
测试结构
遵循 Arrange-Act-Assert(AAA)或 Given-When-Then 模式:
- Arrange/Given:准备输入与依赖。
- Act/When:调用被测单元。
- Assert/Then:验证输出或副作用。
边缘用例
在正常路径通过后,为以下情况添加测试:
- 边界值(空集合、零、null/undefined、最大值)。
- 错误条件(无效输入、缺失数据、异常)。
只添加与所构建的具体逻辑相关的边缘用例测试。
运行完整相关测试套件
所有改动完成后,运行受影响模块或包的完整测试套件,确保没有回归。
不得跳过测试
绝不为了节省时间而跳过测试。只要在创建代码,就要创建测试。不存在编写生产代码却没有对应测试的场景。如果时间紧张,就缩小范围——但每一行已实现的范围都必须有测试覆盖。
示例:简单函数的 TDD 循环
步骤 1——写失败测试:
test("adds two positive numbers", () => { expect(add(2, 3)).toBe(5); });
步骤 2——最小实现:
function add(a, b) { return a + b; }
步骤 3——测试通过。如有需要则重构(此处无需重构)。
步骤 4——下一个测试:
test("returns 0 when both arguments are 0", () => { expect(add(0, 0)).toBe(0); });
步骤 5——已经通过。进入下一个行为。
示例:处理已有测试
场景:需要把某个函数的返回类型从 number 改为 string。
- 运行已有测试——它们通过。
- 找出哪些断言会因这次有意的变更而失败。
- 只更新那些断言,使其期望新的 string 返回类型。
- 确认更新后的测试失败(因为实现尚未改变)。
- 修改实现。
- 确认所有测试通过。
适用场景与限制
该技能适用于任何需要编写或修改代码并保证正确性的开发任务,尤其是缺陷修复、重构和新增功能。其限制在于:技能本身只规定流程与测试规范,不涉及具体测试框架、语言或运行环境的配置;资料中未提供安装命令、依赖要求、支持的平台或收费信息。