mcpField
T
tdd

tdd

github.com/inkeep测试与质量48 次安装
代码仓库

关于 tdd

tdd 是一项适用于不同语言和测试框架的测试驱动开发方法技能,强调通过公共接口验证可观察行为。它为开发者提供逐步编写测试与实现的指导,避免测试绑定内部细节。

主要功能

  1. 01通过公共接口验证行为
  2. 02仅在系统边界使用模拟
  3. 03按垂直切片逐步推进
  4. 04以单条端到端测试启动
  5. 05每轮先通过测试再重构

适用场景

  1. 01为用户可见行为编写测试
  2. 02在重构时减少脆弱测试
  3. 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

评论

支持 Markdown