关于 tdd
tdd 是一项适用于不同语言和测试框架的测试驱动开发方法技能,强调通过公共接口验证可观察行为。它为开发者提供逐步编写测试与实现的指导,避免测试绑定内部细节。
主要功能
- 01通过公共接口验证行为
- 02仅在系统边界使用模拟
- 03按垂直切片逐步推进
- 04以单条端到端测试启动
- 05每轮先通过测试再重构
适用场景
- 01为用户可见行为编写测试
- 02在重构时减少脆弱测试
- 03逐步开发并验证功能
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ inkeep/ team-skills --skill tdd安装后在 Claude Code 中输入/tdd确认已加载。
详细介绍
测试驱动开发方法论
编写优质测试的通用原则。无论使用哪种框架、语言或代码库,这些原则都适用——请从项目配置、贡献指南、现有测试文件、CI/CD 配置以及任何代码库级 AI 技能或规则中,了解并遵循代码库特定的测试工具、命令和约定。
核心原则
测试通过公共接口验证行为,而非实现细节。代码可以彻底改变;除非行为发生变化,否则测试不应失败。
优质测试通过公共 API 覆盖真实代码路径。它们描述系统_做什么_,而不是_怎么做_。优质测试读起来就像一份规范——“用户可以使用有效购物车完成结账”准确说明了系统具备什么能力。这类测试经得起重构,因为它们不关心内部结构。
劣质测试与实现耦合。它们会模拟内部协作者、测试私有方法,或通过外部方式验证(例如,直接查询数据库,而不是使用接口)。一个警示信号是:重构时测试会失败,但行为并未改变。
优质测试与劣质测试
// GOOD: Tests observable behavior through the interface
test("created user is retrievable", async () => {
const user = await createUser({ name: "Alice" });
const retrieved = await getUser(user.id);
expect(retrieved.name).toBe("Alice");
});
// BAD: Bypasses interface to verify via implementation detail
test("createUser saves to database", async () => {
await createUser({ name: "Alice" });
const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]);
expect(row).toBeDefined();
});
// GOOD: Tests the outcome
test("user can checkout with valid cart", async () => {
const cart = createCart();
cart.add(product);
const result = await checkout(cart, paymentMethod);
expect(result.status).toBe("confirmed");
});
// BAD: Tests the mechanism
test("checkout calls paymentService.process", async () => {
const mockPayment = vi.spyOn(paymentService, "process");
await checkout(cart, payment);
expect(mockPayment).toHaveBeenCalledWith(cart.total);
});
实现耦合型测试的危险信号:
- 模拟内部协作者(而非外部边界)
- 测试私有方法
- 断言调用次数或调用顺序
- 行为未改变,测试却因重构而失败
- 测试名称描述的是 HOW(“调用 X”),而不是 WHAT(“用户可以 Y”)
模拟原则
只在系统边界进行模拟:
- 外部 API(支付、电子邮件、第三方服务)
- 数据库(有时——条件允许时,优先使用测试数据库)
- 时间/随机性
- 文件系统(有时)
不要模拟自己的模块、内部协作者或任何由你控制的内容。如果你需要模拟内部模块才能测试某项功能,这说明接口设计需要改进,而不是需要更多模拟。
设计时应考虑在边界进行模拟:
// Easy to mock — dependency is injected
function processPayment(order, paymentClient) {
return paymentClient.charge(order.total);
}
// Hard to mock — dependency is internal
function processPayment(order) {
const client = new StripeClient(process.env.STRIPE_KEY);
return client.charge(order.total);
}
垂直切片(反模式:水平切片)
不要先编写所有测试,再编写所有实现。这就是“水平切片”——它会产生薄弱的测试:
- 批量编写的测试检验的是_想象中的_行为,而非_实际的_行为
- 最终测试的是事物的_形式_,而非面向用户的行为
- 测试会对实际变化不敏感——行为出问题时仍然通过,行为正常时反而失败
- 你会在理解实现之前就确定测试结构
正确做法——通过示踪弹逐步进行垂直切片:
WRONG (horizontal):
RED: test1, test2, test3, test4, test5
GREEN: impl1, impl2, impl3, impl4, impl5
RIGHT (vertical):
RED→GREEN: test1→impl1
RED→GREEN: test2→impl2
RED→GREEN: test3→impl3
每个测试都基于上一个周期的经验。因为你刚写完代码,所以清楚哪些行为至关重要,以及如何验证它们。
示踪弹
从一个能证明一条路径端到端正常工作的测试开始:
RED: Write test for first behavior → test fails
GREEN: Write minimal code to pass → test passes
这就是你的示踪弹——它能在你扩展测试覆盖面之前,证明路径正常工作。然后逐步添加行为,一次编写一个测试。
规则:
- 一次只编写一个测试
- 只编写足以通过当前测试的代码
- 不要预判未来的测试
- 让测试聚焦于可观察的行为
- RED 状态下绝不重构——先达到 GREEN
每周期检查清单
[ ] Test describes behavior, not implementation
[ ] Test uses public interface only
[ ] Test would survive an internal refactor
[ ] Code is minimal for this test
[ ] No speculative features added