mcpField
B
behavioral-testing

behavioral-testing

github.com/caidanw测试与质量1145 次安装11 周前更新
代码仓库

关于 behavioral-testing

behavioral-testing 是指导编写简洁行为测试的技能,面向测试编写者和审查者。它聚焦用户可观察结果和系统边界,避免测试实现细节,并提供按需查阅的测试参考。

主要功能

  1. 01围绕用户可见行为编写测试
  2. 02采用准备、执行、断言模式
  3. 03覆盖边界情况与错误恢复
  4. 04仅模拟外部依赖
  5. 05提供测试模板与覆盖参考

适用场景

  1. 01为表单输入和错误提示编写测试
  2. 02验证状态转换及 API 故障恢复
  3. 03检查测试是否依赖实现细节

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/caidanw/skills --skill behavioral-testing

安装后在 Claude Code 中输入/behavioral-testing确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
anti-patterns.md
branch-coverage.md
test-templates.md

详细介绍

行为测试

测试可观察到的行为。保持测试简洁。绝不测试实现细节。

核心原则

1. Test what the user sees, not how the code works
2. If a refactor breaks a test, the test was wrong
3. Mocks isolate — they are never the thing being tested
4. Every assertion must trace to a user-observable outcome
5. Terse tests > thorough ceremony

编写测试

测试模式
Arrange: Set up the minimum preconditions
Act:     Do the thing the user would do
Assert:  Check what the user would see

就这么简单。不需要红绿红的仪式。不需要冗长的设置。如果测试设置比断言还长,那就有问题。

测试内容

测试边界处的行为,而不是代码的每一行:

始终测试跳过
输入为空、为 null 或仅含空白字符时会发生什么内部方法的调用顺序
用户能看到的错误消息调用了哪个辅助函数
状态转换(加载 → 成功 → 错误)状态管理的实现细节
API 失败后的恢复模拟调用次数(除非这就是要测试的行为)
用户会遇到的边缘情况仅覆盖成功路径
测试命名

根据行为而不是函数来命名测试:

✅ "shows error when email is empty"
✅ "redirects to login after session expires"
✅ "prevents duplicate submission on double-click"

❌ "test validateEmail"
❌ "test handleSubmit calls api"
❌ "test useAuth hook returns null"
保持测试简洁
// ✅ Terse — tests one behavior, reads in 3 seconds
test('shows error when email is empty', () => {
  render(<LoginForm />);
  click(submitButton());
  expect(screen.getByText('Email is required')).toBeVisible();
});

// ❌ Verbose — ceremony obscures intent
test('should display an error message when the user submits the form without entering an email address', () => {
  const mockOnSubmit = vi.fn();
  const mockOnError = vi.fn();
  const { container } = render(
    <LoginForm onSubmit={mockOnSubmit} onError={mockOnError} />
  );
  const form = container.querySelector('form');
  const button = screen.getByRole('button', { name: /submit/i });
  await userEvent.click(button);
  expect(mockOnSubmit).not.toHaveBeenCalled();
  expect(mockOnError).toHaveBeenCalledWith(expect.objectContaining({ field: 'email' }));
  expect(screen.getByText('Email is required')).toBeVisible();
});

简洁的测试能捕获同样的 bug。冗长的测试还会断言模拟对象的内部细节——即使行为没有变化,这些断言也会在重构时失效。

停止检查

编写或审查任何测试前,先进行以下检查:

□ Am I asserting on a mock instead of real output?
  → If yes: delete the assertion or unmock it

□ Would a refactor break this test even though behavior hasn't changed?
  → If yes: the test is coupled to implementation — rewrite it

□ Is mock setup > 50% of the test?
  → If yes: use an integration test with real components instead

□ Does this test name describe a user-visible behavior?
  → If no: rename it or question whether it needs to exist

□ Did I write this test after the implementation?
  → If yes: verify it actually fails when behavior is broken, not just when code changes

何时使用模拟

只模拟外部边界。 网络调用、第三方服务、计时器——这些都是你无法控制的事物。

绝不模拟内部模块、你自己编写的组件,或仅仅是“以防万一”而进行的模拟。

如果必须模拟某个对象才能测试它,说明设计存在耦合问题——修复设计,而不是测试。

详细参考

仅在需要时加载:

  • references/anti-patterns.md — 常见测试错误及示例:测试模拟行为、仅供测试使用的生产方法、不完整的模拟、过度模拟
  • references/test-templates.md — 单元、集成和 E2E 测试的可复制粘贴模式。工厂、辅助工具和简洁的断言模式
  • references/branch-coverage.md — 系统化覆盖的分支矩阵方法:如何映射条件、确定优先级,并在不测试每种排列组合的情况下验证完整性

评论

支持 Markdown