帮助开发者识别并避免常见测试反模式,提升单元测试可维护性与可靠性。
复制安装指令,让 AI 自动完成配置 · 推荐新手
请帮我安装 askskill 上的 "Testing Anti-Patterns" 技能: 1. 下载 https://raw.githubusercontent.com/obra/clank/main/skills/testing/testing-anti-patterns/SKILL.md 2. 保存为 ~/.claude/skills/testing-anti-patterns/SKILL.md 3. 装好后重载技能,告诉我可以用了
请检查下面这组单元测试是否存在测试反模式,重点关注:是否在测试 mock 的行为、是否为了测试给生产类添加了仅测试用方法、是否在未理解依赖关系前就盲目 mock。请逐条指出问题并给出改进建议。
得到一份测试问题清单,并附带每项反模式的原因与重构建议。
下面的测试过度依赖 mock,导致实现一改就失败。请按“减少对 mock 行为断言、改为验证业务结果、保留必要依赖隔离”的原则重写这些测试,并解释修改原因。
获得一版更稳健的测试代码,以及为何这样重构的说明。
我正在为一个服务类写测试。请根据以下依赖关系分析哪些依赖应该 mock、哪些不该 mock,并说明判断依据,例如外部系统、稳定值对象、数据库访问、时间或随机数等。
得到一份依赖分类建议,帮助选择合适的测试边界与 mock 策略。
Tests must verify real behavior, not mock behavior. Mocks are a means to isolate, not the thing being tested.
Core principle: Test what the code does, not what the mocks do.
Following strict TDD prevents these anti-patterns.
1. NEVER test mock behavior
2. NEVER add test-only methods to production classes
3. NEVER mock without understanding dependencies
The violation:
// ❌ BAD: Testing that the mock exists
test('renders sidebar', () => {
render(<Page />);
expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument();
});
Why this is wrong:
your human partner's correction: "Are we testing the behavior of a mock?"
The fix:
// ✅ GOOD: Test real component or don't mock it
test('renders sidebar', () => {
render(<Page />); // Don't mock sidebar
expect(screen.getByRole('navigation')).toBeInTheDocument();
});
// OR if sidebar must be mocked for isolation:
// Don't assert on the mock - test Page's behavior with sidebar present
BEFORE asserting on any mock element:
Ask: "Am I testing real component behavior or just mock existence?"
IF testing mock existence:
STOP - Delete the assertion or unmock the component
Test real behavior instead
The violation:
// ❌ BAD: destroy() only used in tests
class Session {
async destroy() { // Looks like production API!
await this._workspaceManager?.destroyWorkspace(this.id);
// ... cleanup
}
}
// In tests
afterEach(() => session.destroy());
Why this is wrong:
The fix:
// ✅ GOOD: Test utilities handle test cleanup
// Session has no destroy() - it's stateless in production
// In test-utils/
export async function cleanupSession(session: Session) {
const workspace = session.getWorkspaceInfo();
if (workspace) {
await workspaceManager.destroyWorkspace(workspace.id);
}
}
// In tests
afterEach(() => cleanupSession(session));
BEFORE adding any method to production class:
Ask: "Is this only used by tests?"
IF yes:
STOP - Don't add it
Put it in test utilities instead
Ask: "Does this class own this resource's lifecycle?"
IF no:
STOP - Wrong class for this method
The violation:
// ❌ BAD: Mock breaks test logic
test('detects duplicate server', () => {
// Mock prevents config write that test depends on!
vi.mock('ToolCatalog', () => ({
discoverAndCacheTools: vi.fn().mockResolvedValue(undefined)
}));
await addServer(config);
await addServer(config); // Should throw - but won't!
});
Why this is wrong:
The fix:
// ✅ GOOD: Mock at correct level
test('detects duplicate server', () => {
// Mock the slow part, preserve behavior test needs
vi.mock('MCPServerManager'); // Just mock slow server startup
await addServer(config); // Config written
await addServer(config); // Duplicate detected ✓
});
BEFORE mocking any method:
STOP - Don't mock yet
1. Ask: "What side effects does the real method have?"
2. Ask: "Does this test depend on any of those side effects?"
3. Ask: "Do I fully understand what this test needs?"
IF depends on side effects:
…
帮助你为变量选择清晰准确、易维护的命名,提升代码可读性。
帮助开发者用先写测试再实现代码的方式提升质量与可维护性。