mcpField
T
testing-anti-patterns

testing-anti-patterns

github.com/jackspace测试与质量1636 次安装12 周前更新
代码仓库

关于 testing-anti-patterns

testing-anti-patterns 是一项介绍测试反模式的技能,面向编写和维护软件测试的开发者。它强调验证真实行为、谨慎使用 Mock,并以 TDD 和集成测试减少测试失真。

主要功能

  1. 01避免断言 Mock 本身
  2. 02避免在生产类中加入测试专用方法
  3. 03理解依赖后再使用 Mock
  4. 04让 Mock 数据匹配真实结构
  5. 05将测试纳入实现流程
  6. 06识别复杂 Mock 的风险

适用场景

  1. 01审查测试是否验证真实组件行为
  2. 02重构依赖过多 Mock 的测试
  3. 03在实现功能前建立 TDD 测试流程

安装

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

$npx skills add https://github.com/jackspace/claudeskillz --skill testing-anti-patterns

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

附带文件SKILL.json

详细介绍

测试反模式

概述

测试必须验证真实行为,而不是模拟行为。Mock 是用于隔离的手段,而不是测试对象。

核心原则: 测试代码的行为,而不是 Mock 的行为。

严格遵循 TDD 可避免这些反模式。

铁律

1. NEVER test mock behavior
2. NEVER add test-only methods to production classes
3. NEVER mock without understanding dependencies

反模式 1:测试 Mock 行为

违规做法:

// ❌ BAD: Testing that the mock exists
test('renders sidebar', () => {
  render(<Page />);
  expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument();
});

这为什么不对:

  • 你验证的是 Mock 能否正常工作,而不是组件能否正常工作
  • Mock 存在时测试通过,不存在时测试失败
  • 无法让你了解任何真实行为

你的搭档提出的纠正问题:“我们是在测试 Mock 的行为吗?”

修正方法:

// ✅ 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

反模式 2:在生产代码中添加仅供测试使用的方法

违规做法:

// ❌ 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());

这为什么不对:

  • 生产类中混入了仅供测试使用的代码
  • 如果在生产环境中意外调用,会很危险
  • 违反 YAGNI 和关注点分离原则
  • 混淆了对象生命周期与实体生命周期

修正方法:

// ✅ 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

反模式 3:未理解就使用 Mock

违规做法:

// ❌ 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!
});

这为什么不对:

  • 被 Mock 的方法有测试所依赖的副作用(写入配置)
  • 为了“保险”而过度使用 Mock,会破坏实际行为
  • 测试因错误原因通过,或莫名其妙地失败

修正方法:

// ✅ 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:
    Mock at lower level (the actual slow/external operation)
    OR use test doubles that preserve necessary behavior
    NOT the high-level method the test depends on

  IF unsure what test depends on:
    Run test with real implementation FIRST
    Observe what actually needs to happen
    THEN add minimal mocking at the right level

  Red flags:
    - "I'll mock this to be safe"
    - "This might be slow, better mock it"
    - Mocking without understanding the dependency chain

反模式 4:不完整的 Mock

违规做法:

// ❌ BAD: Partial mock - only fields you think you need
const mockResponse = {
  status: 'success',
  data: { userId: '123', name: 'Alice' }
  // Missing: metadata that downstream code uses
};

// Later: breaks when code accesses response.metadata.requestId

这为什么不对:

  • 不完整的 Mock 会掩盖结构假设——你只模拟了自己知道的字段
  • 下游代码可能依赖你未包含的字段——导致静默失败
  • 测试通过但集成失败——Mock 不完整,真实 API 却是完整的
  • 虚假的信心——测试无法证明任何真实行为

铁律: Mock 必须完整模拟现实中的数据结构,而不只是当前测试直接使用的字段。

修正方法:

// ✅ GOOD: Mirror real API completeness
const mockResponse = {
  status: 'success',
  data: { userId: '123', name: 'Alice' },
  metadata: { requestId: 'req-789', timestamp: 1234567890 }
  // All fields real API returns
};
闸门函数
BEFORE creating mock responses:
  Check: "What fields does the real API response contain?"

  Actions:
    1. Examine actual API response from docs/examples
    2. Include ALL fields system might consume downstream
    3. Verify mock matches real response schema completely

  Critical:
    If you're creating a mock, you must understand the ENTIRE structure
    Partial mocks fail silently when code depends on omitted fields

  If uncertain: Include all documented fields

反模式 5:事后才考虑集成测试

违规做法:

✅ Implementation complete
❌ No tests written
"Ready for testing"

这为什么不对:

  • 测试是实现的一部分,而不是可有可无的后续工作
  • TDD 本可以发现这个问题
  • 没有测试,就不能声称工作已完成

修正方法:

TDD cycle:
1. Write failing test
2. Implement to pass
3. Refactor
4. THEN claim complete

Mock 变得过于复杂时

警示信号:

  • Mock 设置比测试逻辑还长
  • 为了让测试通过而 Mock 一切
  • Mock 缺少真实组件所具备的方法
  • Mock 变更时测试也会失败

你的搭档提出的问题:“这里真的需要使用 Mock 吗?”

请考虑: 使用真实组件的集成测试往往比复杂的 Mock 更简单

TDD 可避免这些反模式

TDD 的作用:

  1. 先写测试 → 迫使你思考自己实际在测试什么
  2. 观察测试失败 → 确认测试的是实际行为,而不是 Mock
  3. 采用最少实现 → 避免混入仅供测试使用的方法
  4. 使用真实依赖 → 在使用 Mock 之前,先弄清测试实际需要什么

如果你在测试 Mock 的行为,就违背了 TDD——你添加 Mock 前,没有先观察测试针对真实代码失败。

快速参考

反模式修正方法
对 Mock 元素进行断言测试真实组件,或移除 Mock
在生产代码中添加仅供测试使用的方法移到测试工具中
未理解就使用 Mock先理解依赖,再尽量少用 Mock
不完整的 Mock完整镜像真实 API
事后才考虑测试使用 TDD——先写测试
过于复杂的 Mock考虑集成测试

危险信号

  • 断言检查 *-mock 测试 ID
  • 某些方法只在测试文件中调用
  • Mock 设置占测试内容的 50% 以上
  • 移除 Mock 后测试失败
  • 无法解释为什么需要 Mock
  • 只是“为了保险”而使用 Mock

归根结底

Mock 是用于隔离的工具,而不是测试对象。

如果 TDD 揭示你在测试 Mock 的行为,说明你走错了方向。

修正方法:测试真实行为,或质疑自己为何要使用 Mock。

评论

支持 Markdown