Skills tdd-workflow
Use when implementing any feature or bugfix, before writing implementation code - write the test first, watch it fail, write minimal code to pass; ensures tests actually verify behavior by requiring failure first. Enforces RED-GREEN-REFACTOR cycle with iron-law compliance.
install
source · Clone the upstream repo
git clone https://github.com/huangwb8/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/huangwb8/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/awesome-code/agents/tdd-workflow" ~/.claude/skills/huangwb8-skills-tdd-workflow && rm -rf "$T"
manifest:
awesome-code/agents/tdd-workflow/SKILL.mdsource content
TDD Workflow - 测试驱动开发工作流
与 bensz-collect-bugs 的协作约定
- 因本 skill 设计缺陷导致的 bug,先用
规范记录到bensz-collect-bugs
,不要直接修改用户本地已安装的 skill 源码;若有 workaround,先记 bug,再继续完成任务。~/.bensz-skills/bugs/ - 只有用户明确要求“report bensz skills bugs”等公开上报时,才用本地
上传新增 bug 到gh
;不要 pull / clone 整个仓库。huangwb8/bensz-bugs
铁律
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
违反规则的信件就是违反规则的精神。
无例外:
- 不保留为"参考"
- 写测试时"不调整"
- 不看它
- 删除=删除
常见合理化
| 借口 | 现实 |
|---|---|
| "太简单不需要测试" | 简单代码也会坏。测试只需 30 秒 |
| "我之后再测试" | 测试立即通过证明不了什么 |
| "测试后达到相同目的" | 测试后="代码做什么?"测试前="代码应该做什么?" |
| "已经手动测试了" | 临时≠系统化。无记录,无法重新运行 |
| "删除 X 小时工作是浪费" | 沉没成本谬误。保留未验证代码是技术债 |
| "保留参考,先写测试" | 你会调整它。那是测试后。删除=删除 |
红色标志 - 停止并重新开始
- 测试前有代码
- "已经手动测试了"
- "测试后达到相同目的"
- "是精神而非仪式"
- "只此一次"的合理化
- "保留为参考"
- 写测试时"不调整"
所有这些意味着:删除代码。用 TDD 重新开始。
核心理念
测试驱动开发(Test-Driven Development, TDD) 是一种先编写测试,再编写实现代码的开发方法。通过严格的 Red-Green-Refactor 循环,确保代码质量和可维护性。
TDD 循环
┌─────────────────────────────────────────────────────────┐ │ 1. RED : 编写失败的测试 │ │ 2. GREEN : 编写最简单的代码使测试通过 │ │ 3. REFACTOR: 在测试保护下重构代码 │ │ 4. 重复循环 │ └─────────────────────────────────────────────────────────┘
何时使用本技能
在以下场景时激活:
- 用户明确要求使用 TDD 或 测试驱动开发
- 需要编写新功能或修复 Bug
- 提到"测试"、"单元测试"、"测试覆盖率"
- 需要确保代码质量
- 重构现有代码(先补充测试)
TDD 工作流程
步骤 1:理解需求
在开始编码前,明确:
- 功能需求:这个功能要做什么?
- 验收标准:如何判断功能正确?
- 边界条件:有哪些特殊情况?
- 错误处理:异常情况如何处理?
步骤 2:编写失败测试(RED)
测试先行原则:
- 先写测试,不写实现
- 运行测试,确认失败(证明测试有效)
- 阅读错误信息,理解预期
测试命名规范(AAA 模式):
# Should_预期行为_When_测试条件 def should_return_user_when_id_exists(): # Arrange(准备) user_id = 123 expected_user = User(id=123, name="Alice") # Act(执行) result = user_service.get_by_id(user_id) # Assert(断言) assert result.id == expected_user.id assert result.name == expected_user.name
步骤 3:最小化实现(GREEN)
最简单的可工作代码:
- 只写足够使测试通过的代码
- 不追求完美,追求通过
- 硬编码可以接受(第一步)
# 最初版本 - 硬编码也可以 def get_by_id(user_id): if user_id == 123: return User(id=123, name="Alice") return None
步骤 4:运行测试确认通过
# 运行测试 pytest tests/test_user_service.py -v # 期望输出 ✅ should_return_user_when_id_exists PASSED
步骤 5:重构代码(REFACTOR)
在测试保护下优化:
- 消除重复
- 提取方法
- 改善命名
- 优化结构
# 重构后版本 def get_by_id(user_id): return _user_repository.find_by_id(user_id)
步骤 6:重复循环
每个功能点重复上述步骤,直到功能完整。
测试质量标准
必须遵守的规则
- ✅ 测试覆盖率 ≥ 80%
- ✅ 每个测试用例独立(不依赖其他测试)
- ✅ 测试可重复(多次运行结果一致)
- ✅ 测试命名清晰(描述意图)
- ✅ 遵循 AAA 模式(Arrange-Act-Assert)
禁止的反模式
- ❌ 伪测试:测试代码没有断言
- ❌ 万能测试:一个测试验证太多东西
- ❌ 测试内部实现:应该测试行为,不是实现细节
- ❌ 脆弱测试:依赖外部状态(时间、随机数等)
TDD 最佳实践
1. 小步前进
- 一次只写一个测试
- 一次只实现一个功能点
- 频繁运行测试(每 1-2 分钟)
2. 测试隔离
# 好的示例 - 使用 fixtures @pytest.fixture def clean_database(): db.reset() yield db.cleanup() def test_create_user(clean_database): user = user_service.create("Alice") assert user.name == "Alice"
3. 测试边界条件
def test_get_by_id(): # 正常情况 assert get_user(1) is not None # 边界条件 assert get_user(0) is None assert get_user(-1) is None assert get_user(999999) is None
4. 测试异常情况
def test_create_user_with_duplicate_email(): with pytest.raises(DuplicateEmailError): user_service.create("alice@example.com") user_service.create("alice@example.com")
不同语言的 TDD 示例
Python (pytest)
# 测试 def should_calculate_total_price(): cart = ShoppingCart() cart.add_item(Item(name="Book", price=10)) cart.add_item(Item(name="Pen", price=5)) assert cart.total_price() == 15 # 实现 class ShoppingCart: def __init__(self): self.items = [] def add_item(self, item): self.items.append(item) def total_price(self): return sum(item.price for item in self.items)
JavaScript (Jest)
// 测试 test('should calculate total price', () => { const cart = new ShoppingCart(); cart.addItem({ name: 'Book', price: 10 }); cart.addItem({ name: 'Pen', price: 5 }); expect(cart.totalPrice()).toBe(15); }); // 实现 class ShoppingCart { constructor() { this.items = []; } addItem(item) { this.items.push(item); } totalPrice() { return this.items.reduce((sum, item) => sum + item.price, 0); } }
TypeScript (Jest)
// 测试 test('should calculate total price', () => { const cart = new ShoppingCart(); cart.addItem({ name: 'Book', price: 10 }); cart.addItem({ name: 'Pen', price: 5 }); expect(cart.totalPrice()).toBe(15); }); // 实现 interface Item { name: string; price: number; } class ShoppingCart { private items: Item[] = []; addItem(item: Item): void { this.items.push(item); } totalPrice(): number { return this.items.reduce((sum, item) => sum + item.price, 0); } }
常见问题
Q1: 是否需要 100% 测试覆盖率?
A: 不一定。80-90% 是合理目标。以下情况可以例外:
- UI 组件(优先用 E2E 测试)
- 简单的 getter/setter
- 第三方库的封装
Q2: 如何测试私有方法?
A: 不要直接测试私有方法。应该通过公共接口测试其行为。如果私有方法太复杂,考虑提取到独立的类。
Q3: TDD 会降低开发速度吗?
A: 短期可能稍慢,但长期来看:
- 减少调试时间
- 减少回归 Bug
- 提高代码可维护性
- 整体效率提升 30-50%
Q4: 什么时候不适合 TDD?
A:
- 探索性原型(POC)
- UI 设计探索
- 紧急热修复(但仍应事后补充测试)
验证清单
完成 TDD 开发后,检查:
- 所有测试通过
- 测试覆盖率 ≥ 80%
- 每个测试用例独立且可重复
- 测试命名清晰(Should_ExpectedBehavior_When_StateUnderTest)
- 遵循 AAA 模式
- 无伪测试(所有测试都有断言)
- 边界条件已测试
- 异常情况已测试