跳转到内容

测试策略:真实逻辑优先

测试策略:真实逻辑优先

学习目标

本章聚焦 LibreChat 的测试哲学。你将了解:

  • 为什么优先使用真实依赖而非 Mock
  • Spy 与 Mock 的选择策略
  • MongoDB 内存服务器 vs Mock DB
  • MCP 真实 SDK 导出 vs 模拟
  • 测试分层与集成测试的组织

前置知识

  • Jest 测试框架基础
  • 单元测试与集成测试的区别

项目实践

测试分层

LibreChat 的测试分为三层:

层次工具文件模式覆盖范围
单元测试Jest*.spec.ts单个函数/类的输入输出
集成测试Jest*.integration.spec.ts多模块协作
E2EPlaywrighte2e/specs/完整用户流程

Mock 策略

LibreChat 的 Mock 优先级原则:

真实逻辑 > Spy > Mock > Stub
1. 优先使用真实依赖(MongoDB 内存服务器、真实 MCP SDK)
2. 仅在无法控制的外部系统使用 Mock(HTTP API、系统调用)
3. 用 Spy 验证调用参数和频率,不替换逻辑

Spy 优于 Mock

// ✅ Spy:保留原始逻辑,仅记录调用
const spy = jest.spyOn(db, 'getConvoFiles');
await initializeAgent(params, db);
expect(spy).toHaveBeenCalledWith(conversationId);
// ❌ Mock:替换原始逻辑
jest.spyOn(db, 'getConvoFiles').mockResolvedValue([]);

Spy 的优势是:如果 getConvoFiles 的内部逻辑有 bug,Spy 仍然会执行它并暴露问题,而 Mock 会跳过。

MongoDB 内存服务器

import { MongoMemoryServer } from 'mongodb-memory-server';
const mongo = await MongoMemoryServer.create();
const uri = mongo.getUri();
// 使用真实 MongoDB 实例进行测试

这比 Mock DB 调用更真实——实际的查询语法、索引约束、聚合管道都会被执行。如果 schema 验证有 bug,内存服务器会暴露出来,而 Mock 不会。

MCP 真实 SDK 导出

import { CallToolResultSchema, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';

测试中使用真实的 @modelcontextprotocol/sdk 导出,而非模拟 MCP 协议的行为。这确保了:

  • 工具调用格式符合 MCP 规范
  • 错误码使用标准值
  • Schema 验证与实际一致

集成测试组织

集成测试文件使用 *.integration.spec.ts 命名约定,与普通单元测试区分。

典型集成测试场景:

  • capabilities.integration.spec.ts:中间件能力检查的完整流程
  • bulk-parity.spec.ts:批量操作与单条操作的等价性验证

问题与规避

问题 1:内存 MongoDB 与生产 MongoDB 版本不一致

规避mongodb-memory-server 可以指定 MongoDB 版本。CI 中使用的版本应与生产环境一致。开发环境可以使用最新版本以便快速启动。

问题 2:过度 Mock 导致测试通过但实际代码有 bug

规避:这是 LibreChat 选择”真实逻辑优先”的核心原因。减少 Mock 使用,增加 Spy 和真实依赖的比例。仅在测试不稳定的外部依赖(如 HTTP API)时使用 Mock。

问题 3:集成测试运行时间长

规避:集成测试在 CI 中并行运行。Jest 的 --testTimeout 配置给集成测试更长的超时时间。本地开发时可以仅运行单元测试快速反馈。


设计取舍

真实逻辑优先 vs 速度优先

方案优势代价
真实逻辑(LibreChat)高置信度、发现真实 bug测试运行慢、环境依赖
速度优先(大量 Mock)测试运行快、环境无关测试通过但实际有 bug

LibreChat 选择了真实逻辑优先,因为:

  1. 项目是 AI 应用,交互逻辑复杂,Mock 容易遗漏边界情况
  2. MongoDB 内存服务器启动仅需几秒,成本可接受
  3. E2E 测试已经覆盖了完整流程,单元测试应聚焦真实逻辑

参考来源