测试反模式避免技巧Skill testing-anti-patterns

本技能专注于识别和避免软件测试中的常见反模式,如测试模拟行为、向生产代码添加仅测试方法、不理解依赖就模拟等。通过遵循TDD原则和最佳实践,帮助开发人员提高测试质量、代码可靠性和软件可维护性。关键词:软件测试、反模式、TDD、模拟、单元测试、集成测试、测试驱动开发、代码质量、测试最佳实践。

测试 0 次安装 0 次浏览 更新于 3/24/2026

name: testing-anti-patterns description: 在编写或修改测试、添加模拟或诱惑向生产代码添加仅测试方法时使用 - 防止测试模拟行为、生产代码被仅测试方法污染以及不理解依赖关系就进行模拟

测试反模式

概述

测试必须验证真实行为,而不是模拟行为。模拟是隔离的手段,而不是被测试的东西。

核心原则: 测试代码做什么,而不是模拟做什么。

遵循严格的TDD可以防止这些反模式。

铁律

1. 绝不测试模拟行为
2. 绝不向生产类添加仅测试方法
3. 绝不不理解依赖关系就进行模拟

反模式1:测试模拟行为

违规示例:

// ❌ 错误:测试模拟是否存在
test('渲染侧边栏', () => {
  render(<Page />);
  expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument();
});

为什么这是错误的:

  • 你在验证模拟是否工作,而不是组件是否工作
  • 当模拟存在时测试通过,不存在时失败
  • 没有告诉你真实行为的信息

你的伙伴的纠正: “我们是在测试模拟的行为吗?”

修复方法:

// ✅ 正确:测试真实组件或不模拟它
test('渲染侧边栏', () => {
  render(<Page />);  // 不模拟侧边栏
  expect(screen.getByRole('navigation')).toBeInTheDocument();
});

// 或者如果必须为隔离而模拟侧边栏:
// 不要断言模拟 - 测试有侧边栏时Page的行为

门控函数

在对任何模拟元素进行断言之前:
  问:“我是在测试真实组件行为还是仅仅测试模拟存在?”

  如果测试模拟存在:
    停止 - 删除断言或取消模拟组件

  测试真实行为代替

反模式2:生产代码中的仅测试方法

违规示例:

// ❌ 错误:destroy()仅在测试中使用
class Session {
  async destroy() {  // 看起来像生产API!
    await this._workspaceManager?.destroyWorkspace(this.id);
    // ... 清理
  }
}

// 在测试中
afterEach(() => session.destroy());

为什么这是错误的:

  • 生产类被仅测试代码污染
  • 如果在生产中意外调用会很危险
  • 违反YAGNI和关注点分离
  • 混淆对象生命周期与实体生命周期

修复方法:

// ✅ 正确:测试工具处理测试清理
// Session没有destroy() - 在生产中它是无状态的

// 在test-utils/
export async function cleanupSession(session: Session) {
  const workspace = session.getWorkspaceInfo();
  if (workspace) {
    await workspaceManager.destroyWorkspace(workspace.id);
  }
}

// 在测试中
afterEach(() => cleanupSession(session));

门控函数

在向生产类添加任何方法之前:
  问:“这仅在测试中使用吗?”

  如果是:
    停止 - 不要添加它
    将其放在测试工具中代替

  问:“这个类拥有这个资源的生命周期吗?”

  如果否:
    停止 - 这个方法放在错误的类中

反模式3:不理解就模拟

违规示例:

// ❌ 错误:模拟破坏了测试逻辑
test('检测重复服务器', () => {
  // 模拟防止了测试依赖的配置写入!
  vi.mock('ToolCatalog', () => ({
    discoverAndCacheTools: vi.fn().mockResolvedValue(undefined)
  }));

  await addServer(config);
  await addServer(config);  // 应该抛出异常 - 但不会!
});

为什么这是错误的:

  • 模拟的方法有测试依赖的副作用(写入配置)
  • 过度模拟以“安全”破坏了实际行为
  • 测试因错误原因通过或神秘失败

修复方法:

// ✅ 正确:在正确级别模拟
test('检测重复服务器', () => {
  // 模拟慢速部分,保留测试需要的行为
  vi.mock('MCPServerManager'); // 只模拟慢速服务器启动

  await addServer(config);  // 配置写入
  await addServer(config);  // 检测到重复 ✓
});

门控函数

在模拟任何方法之前:
  停止 - 先不要模拟

  1. 问:“真实方法有什么副作用?”
  2. 问:“这个测试依赖于任何这些副作用吗?”
  3. 问:“我完全理解这个测试需要什么吗?”

  如果依赖于副作用:
    在更低级别模拟(实际慢速/外部操作)
    或使用保留必要行为的测试替身
    不是测试依赖的高级方法

  如果不确定测试依赖什么:
    先用真实实现运行测试
    观察实际需要发生什么
    然后在正确级别添加最小模拟

  红旗:
    - “我会模拟这个以安全”
    - “这可能很慢,最好模拟它”
    - 不理解依赖链就模拟

反模式4:不完整的模拟

违规示例:

// ❌ 错误:部分模拟 - 只包括你认为需要的字段
const mockResponse = {
  status: 'success',
  data: { userId: '123', name: 'Alice' }
  // 缺失:下游代码使用的元数据
};

// 稍后:当代码访问response.metadata.requestId时中断

为什么这是错误的:

  • 部分模拟隐藏结构假设 - 你只模拟了你知道的字段
  • 下游代码可能依赖于你未包括的字段 - 静默失败
  • 测试通过但集成失败 - 模拟不完整,真实API完整
  • 虚假信心 - 测试没有证明任何关于真实行为的信息

铁则: 模拟完整的真实数据结构,而不仅仅是你立即测试使用的字段。

修复方法:

// ✅ 正确:镜像真实API的完整性
const mockResponse = {
  status: 'success',
  data: { userId: '123', name: 'Alice' },
  metadata: { requestId: 'req-789', timestamp: 1234567890 }
  // 真实API返回的所有字段
};

门控函数

在创建模拟响应之前:
  检查:“真实API响应包含什么字段?”

  行动:
    1. 检查文档/示例中的实际API响应
    2. 包括系统可能在下游消耗的所有字段
    3. 验证模拟完全匹配真实响应模式

  关键:
    如果你创建一个模拟,你必须理解整个结构
    当代码依赖于省略的字段时,部分模拟静默失败

  如果不确定:包括所有文档化字段

反模式5:集成测试作为事后想法

违规示例:

✅ 实现完成
❌ 没有编写测试
“准备测试”

为什么这是错误的:

  • 测试是实施的一部分,不是可选的后续
  • TDD会捕获这个问题
  • 没有测试不能声称完成

修复方法:

TDD周期:
1. 编写失败测试
2. 实施以通过
3. 重构
4. 然后声称完成

当模拟变得太复杂时

警告信号:

  • 模拟设置比测试逻辑长
  • 模拟所有以使测试通过
  • 模拟缺少真实组件有的方法
  • 模拟更改时测试中断

你的伙伴的问题: “我们需要在这里使用模拟吗?”

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

TDD防止这些反模式

为什么TDD有帮助:

  1. 先写测试 → 迫使你思考你实际在测试什么
  2. 看它失败 → 确认测试测试真实行为,而不是模拟
  3. 最小实现 → 防止仅测试方法渗入
  4. 真实依赖 → 在模拟之前,你看到测试实际需要什么

如果你在测试模拟行为,你违反了TDD - 你添加了模拟而没有先用真实代码看测试失败。

快速参考

反模式 修复
断言模拟元素 测试真实组件或取消模拟它
生产代码中的仅测试方法 移动到测试工具
不理解就模拟 先理解依赖,最小模拟
不完整的模拟 完全镜像真实API
测试作为事后想法 TDD - 测试先写
过度复杂的模拟 考虑集成测试

红旗

  • 断言检查*-mock测试ID
  • 方法仅在测试文件中调用
  • 模拟设置占测试的>50%
  • 当你移除模拟时测试失败
  • 不能解释为什么需要模拟
  • 模拟“只是为了安全”

底线

模拟是隔离的工具,不是要测试的东西。

如果TDD揭示你在测试模拟行为,你就错了。

修复:测试真实行为或质疑为什么你一开始在模拟。