跳转到内容

测试策略

Gemini CLI — 测试策略

学习目标

  • 理解 Gemini CLI 的多层测试架构(单元、集成、性能)
  • 掌握 TestRig 集成测试夹具的使用模式
  • 分析 MSW 网络拦截和 SVG 快照测试的应用

项目实践

测试层次

测试金字塔:
┌──────────┐
│ 性能测试 │ ← perf-tests/ (少量)
┌┴──────────┴┐
│ 集成测试 │ ← integration-tests/ + evals/ (中等)
┌┴────────────┴┐
│ 单元测试 │ ← *.test.ts (大量)
└──────────────┘
层次文件位置数量级执行时间
单元测试packages/*/src/**/*.test.ts数百秒级
集成测试integration-tests/数十分钟级
性能测试perf-tests/少量分钟级
评估测试evals/数十分钟级

测试基础设施

Vitest:所有包使用 vitest.config.ts 配置测试环境:

packages/core/vitest.config.ts
export default defineConfig({
test: {
setupFiles: ['./test-setup.ts'],
environment: 'node',
globals: true,
},
});

test-setup.ts:每个包的测试环境初始化:

  • Mock 环境变量
  • 设置全局配置
  • 初始化 MSW 服务器

MSW(Mock Service Worker)网络拦截

packages/core/src/mocks/msw.ts 使用 MSW 拦截网络请求:

import { setupServer } from 'msw/node';
import { handlers } from './handlers';
const server = setupServer(...handlers);
// 测试前启动
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
// 测试后清理
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

优势

  • 拦截真实的 fetch 调用,而非 Mock fetch 函数
  • 更接近真实网络行为(包括 URL 匹配、请求体检查)
  • 支持流式响应 Mock(对 LLM API 很重要)

Handler 示例(Gemini API Mock):

http.post('https://generativelanguage.googleapis.com/**', () => {
return HttpResponse.json({
candidates: [{ content: { parts: [{ text: 'Hello!' }] } }]
});
});

TestRig 集成测试夹具

TestRigpackages/test-utils/src/test-rig.ts)提供完整的 AgentLoopContext:

const rig = await TestRig.create({
config: {
model: 'gemini-2.5-flash',
approvalMode: 'yolo',
},
tools: [readFileTool, shellTool, grepTool],
});
await rig.initialize();
// 使用 rig.context 进行集成测试
const client = new GeminiClient(rig.context);
const stream = client.sendMessageStream('Hello', signal, 'test-prompt');
for await (const event of stream) {
// 验证事件流
}

TestRig 提供的功能

  • 完整的 AgentLoopContext(Config、ToolRegistry、MessageBus、Scheduler)
  • Mock 文件系统
  • Mock Gemini API 响应
  • 自动清理(测试后删除临时文件)

Memory Test Harness

MemoryTestHarnesspackages/test-utils/src/memory-test-harness.ts)用于检测内存泄漏:

const harness = new MemoryTestHarness();
harness.start();
// 执行操作
harness.measure();
// 报告内存增长

测量方法

  • 操作前记录 V8 堆快照
  • 操作后记录 V8 堆快照
  • 比较两次快照,识别未释放的对象
  • 检测特定类型的泄漏(如事件监听器未清理)

Perf Test Harness

PerfTestHarnesspackages/test-utils/src/perf-test-harness.ts)用于性能基准测试:

const harness = new PerfTestHarness();
harness.startTimer('tool-execution');
await executeTool(toolCall);
harness.stopTimer('tool-execution');
harness.report();

测量指标

  • 工具执行延迟
  • 上下文渲染时间
  • 模型路由延迟
  • 总 turn 时间

SVG Snapshot Testing

TUI 组件使用 SVG 快照进行可视化回归测试:

test('should render SVG snapshot for a shell tool', async () => {
const { container } = render(<ToolGroup type="shell" command="ls -la" />);
expect(container).toMatchSVGSnapshot();
});

快照文件:存储在 __snapshots__/*.snap.svg 文件中,可通过浏览器直接查看。

为什么不是文本快照

  • 终端 UI 使用 Unicode 字符和 ANSI 颜色代码
  • 文本 diff 无法捕获渲染差异(如边框错位、颜色变化)
  • SVG 快照可以在浏览器中预览,方便审查

Integration Tests

integration-tests/ 目录包含端到端测试:

integration-tests/
├── test-fixtures/ # 测试夹具(项目目录结构)
├── basic-chat.test.ts # 基本对话测试
├── tool-execution.test.ts # 工具执行测试
└── ...

测试模式

  1. 创建临时项目目录(使用 test-fixtures 中的模板)
  2. 启动 Gemini CLI 进程(子进程)
  3. 通过 stdin/stdout 发送输入和读取输出
  4. 验证 Agent 行为(工具调用、文件修改等)

Evals

evals/ 目录包含评估测试,验证 Agent 在特定场景下的行为质量:

评估文件测试内容
memory_persistence.eval.ts记忆持久化的正确性
auto_memory_contract.eval.ts自动记忆提取的契约验证
auto_memory_modes.eval.ts不同记忆模式的行为

陷阱与对策

MSW 与流式响应的兼容性

问题:MSW 默认不支持流式响应 Mock,但 LLM API 返回的是 SSE 流。

对策

  • 使用自定义 handler 模拟 SSE 流
  • 将完整响应分块发送,模拟真实流式行为
  • 或在测试中关闭流式模式,使用非流式 Mock

TestRig 初始化成本高

问题:TestRig 需要初始化完整的 AgentLoopContext,单个测试文件的 setup 代码量大。

对策

  • 使用 beforeAll 共享 TestRig 实例
  • 每个测试用例重置状态而非重新初始化
  • 对于轻量测试,使用 Mock 对象而非 TestRig

SVG 快照的 CI 不稳定性

问题:SVG 快照对终端尺寸和 Unicode 字体敏感,CI 环境与本地可能不同。

对策

  • CI 中设置固定的终端尺寸(process.env.COLUMNS = '120'
  • 快照使用相对渲染,不依赖绝对坐标
  • 定期在 CI 上更新快照,避免 drift

设计取舍

MSW vs 手动 Mock

方案优势代价
MSW拦截真实 fetch、URL 匹配、更接近真实行为需要额外依赖、流式响应需要定制
手动 Mock简单直接、完全控制容易遗漏边界情况、与实际 API 行为可能不一致

Gemini CLI 的选择:MSW。LLM API 的行为复杂(流式、错误格式、重试),手动 Mock 容易与实际行为不一致。


参考来源