mcpField
T
test-antipatterns

test-antipatterns

github.com/pedronauck测试与质量64786 次安装2 周前更新
代码仓库

关于 test-antipatterns

面向开发者的测试 Skill,强调验证真实行为,并仅用 Mock 进行隔离。内容涵盖常见的 Mock 陷阱,以及何时考虑集成测试。

主要功能

  1. 01测试真实行为而非 Mock
  2. 02避免在生产代码加入测试专用方法
  3. 03Mock 前先理解依赖
  4. 04让 Mock 数据结构完整
  5. 05随实现同步编写测试

适用场景

  1. 01检查针对 Mock 元素的断言
  2. 02判断方法是否应移入测试工具
  3. 03排查 Mock 副作用导致的测试问题

安装

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

$npx skills add https://github.com/pedronauck/skills --skill test-antipatterns

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

详细介绍

测试反模式

概述

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

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

始终如一地遵循这些原则,可以避免这些反模式。

铁律

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 时测试失败
  • 无法让你了解真实行为

你的协作伙伴提出的纠正问题:“我们是在测试 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"

这样做为什么不对:

  • 测试是实现的一部分,不是可有可无的后续工作
  • 没有测试就不能声称工作已完成
  • 与实现同步编写测试可以及早发现问题

修复方法:

Tests are part of implementation:
1. Write tests alongside implementation
2. Verify behavior works correctly
3. Refactor as needed
4. THEN claim complete

Mock 变得过于复杂时

警示信号:

  • Mock 配置比测试逻辑还长
  • 为了让测试通过而什么都 Mock
  • Mock 缺少真实组件所具有的方法
  • Mock 一变,测试就失败

你的协作伙伴提出的问题:“这里真的需要使用 Mock 吗?”

不妨考虑: 使用真实组件的集成测试通常比复杂的 Mock 更简单

速查

反模式修复方法
断言 Mock 元素测试真实组件,或取消 Mock
在生产代码中添加仅供测试使用的方法移至测试工具
未理解就使用 Mock先了解依赖项,再尽量少用 Mock
不完整的 Mock完整复现真实 API
把测试当作事后补充测试是实现的一部分
过于复杂的 Mock考虑使用集成测试

危险信号

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

总结

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

如果你在测试 Mock 的行为,就说明方向错了。

修复方法:测试真实行为,或质疑自己是否真的需要使用 Mock。

评论

支持 Markdown