关于 behavioral-testing
behavioral-testing 是指导编写简洁行为测试的技能,面向测试编写者和审查者。它聚焦用户可观察结果和系统边界,避免测试实现细节,并提供按需查阅的测试参考。
主要功能
- 01围绕用户可见行为编写测试
- 02采用准备、执行、断言模式
- 03覆盖边界情况与错误恢复
- 04仅模拟外部依赖
- 05提供测试模板与覆盖参考
适用场景
- 01为表单输入和错误提示编写测试
- 02验证状态转换及 API 故障恢复
- 03检查测试是否依赖实现细节
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ caidanw/ skills --skill behavioral-testing安装后在 Claude Code 中输入/behavioral-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/anti-patterns.mdbranch-coverage.mdtest-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