DeepSeek Harness 是一个大型 pnpm monorepo(49 个插件组、219 个包层包),驱动真实的 LLM、子进程、文件系统与浏览器 GUI。出色的测试是工程的一等公民:仓库把验证任务组织成若干层级(tier),每层有各自的 Vitest 配置、测试位置约定与环境规则。本页完整梳理该矩阵——从单文件 100% 覆盖率的单元测试,到无密钥快照回放与真实 API e2e 冒烟测试——并剖析共享的 test-support 包与 DSH_SNAPSHOT 录制/刷新工作流。
测试层级一览
| 层级 | 命令(package.json) | 配置 | 测试存放位置 | 需要密钥? |
|---|---|---|---|---|
| 单元 | pnpm run test | vitest.config.ts | packages/*/*/tests/**/*.spec.ts、apps/*/tests、examples/*/tests、scripts/**/*.spec.ts | 否 |
| 覆盖率门禁 | pnpm run test:coverage | vitest.config.ts(含 coverage) | 同单元 | 否 |
| 真实 API e2e | pnpm run test:e2e | vitest.e2e.config.ts | **/*.e2e.ts | 是(无密钥自跳过) |
| 无密钥快照 | pnpm run test:snapshot | vitest.snapshot.config.ts | **/*.snapshot.ts | 否 |
| Web 浏览器快照 | pnpm run test:web | vitest.web.config.ts | apps/web/tests/*.{e2e,snapshot}.ts | 否(回放) |
| Web 性能 / 压力 | test:web:perf、test:web:stress | vitest.web.{perf,web-stress}.config.ts | apps/web/tests/*.perf.ts、apps/web/stress-tests/*.stress.ts | 否 |
根目录 package.json 的 scripts 是权威清单。pnpm run test 直接运行 vitest run;各层通过 --config 使用独立配置。这并非单个大 runner——真实 API 测试需要密钥与很长的超时,快照测试在 CI 下绝不能意外写入 golden 文件,浏览器测试要启动整个 Chromium 实例。
test-support 包
共享机制位于 packages/test-support/。每个包还带一个 ./invariant 伴生包(一个指向 lib/invariant.js 的 npm 子路径导出),使其包所有权登记进不变量注册表——test-support 包通常带有空安装器,并附 No runtime invariant: 注释。
| 包 | 作用 |
|---|---|
@deepseek-ai/dsh-agent-loop-testkit | 在 agent-loop 测试前挂载前置服务 |
@deepseek-ai/dsh-llm-mock-server | 可脚本化的 OpenAI 兼容 HTTP/SSE 故障服务器 |
@deepseek-ai/dsh-llm-replay | 回放插件:从录制的会话 JSONL 重建模型分块 |
@deepseek-ai/dsh-client-test-runtime | jsdom “插槽”运行时:真实 Cordis Context + SlotRegistry + web-react 渲染器 |
@deepseek-ai/dsh-loader-smoke | 无密钥示例冒烟的子进程与直连 agent 夹具 |
@deepseek-ai/dsh-acp-snapshot | ACP 快照套件工厂、子进程启动器、归一化器 |
agent-loop-testkit
testkit 不挂载 agent loop 本身——只挂载具体 loop 需要的前置服务,从而让测试掌握加载顺序与拓扑:
// packages/test-support/agent-loop-testkit/src/index.ts
import AgentRegistry from '@deepseek-ai/dsh-agent'
import LlmRuntime from '@deepseek-ai/dsh-llm'
import SessionStore from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRuntime from '@deepseek-ai/dsh-tools'
export async function mountAgentLoopTestDependencies(
ctx: Context,
options: AgentLoopTestDependenciesOptions = {},
): Promise<void> {
await ctx.plugin(LlmRuntime)
await ctx.plugin(SessionStore)
await ctx.plugin(SystemPrompt, options.systemPrompt ?? {})
await ctx.plugin(ToolRuntime, options.tools ?? {})
await ctx.plugin(AgentRegistry)
}llm-mock-server
@deepseek-ai/dsh-llm-mock-server 是一个用于 LLM 恢复测试的可脚本化故障服务器。因为它是 HTTP/SSE 服务器,所以能覆盖真实传输,并针对真实故障模式做加固——connection_reset、stream_disconnect、partial_eof、stall、malformed_event、rate_limit、auth_error、context_overflow、quota_exceeded 等等:
// packages/test-support/llm-mock-server/src/index.ts
export const MOCK_LLM_BEHAVIORS = [
'connection_reset', 'stream_disconnect', 'empty', 'empty_body',
'stream_eof', 'partial_eof', 'partial_disconnect', 'stall',
'malformed_json', 'malformed_event', 'wrong_content_type', 'rate_limit',
'server_error', 'service_unavailable', 'auth_error', 'invalid_request',
'context_overflow', 'quota_exceeded', 'success', 'reasoning_success',
'tool_call_success', 'max_tokens', 'slow_success', 'random',
] as constllm-replay
@deepseek-ai/dsh-llm-replay 是快照测试无密钥方案的核心。它读取录制的会话 JSONL,从 assistant/chunk 事件为每个会话推导一个模型调用脚本,并按首次调用顺序把新的实时会话绑定到这些脚本——于是测试用回放的模型输出启动 真实的 agent loop:
export type ReplayEntry =
| { kind: 'chunks'; chunks: StreamChunk[] }
| { kind: 'throw'; chunks: StreamChunk[]; message: string; code: string }
| { kind: 'hang'; readyFile?: string }acp-snapshot
@deepseek-ai/dsh-acp-snapshot 构建无密钥的 ACP 场景。每个场景驱动真实子进程(启动 acp-agent 示例)并比较归一化后的 stdout;会话 fixture 同时充当回放输入与预期输出。
client-runtime 与 loader-smoke
@deepseek-ai/dsh-client-test-runtime提供 jsdom“插槽”运行时——真实的 CordisContext+SlotRegistry+web-react渲染器——外加测试专属的session/workspace双例,因此客户端功能规格测试无需浏览器进程就能运行。@deepseek-ai/dsh-loader-smoke是无密钥真实 Loader 示例冒烟的共享子进程/直连 agent 夹具。每个示例都附带一个无密钥冒烟测试:通过 Loader 启动真实cordis.yml,驱动它,并断言输出与干净退出。
DSH_SNAPSHOT 工作流
快照配置从 DSH_SNAPSHOT 环境变量选择模式。package.json:
"test:snapshot": "vitest run --config vitest.snapshot.config.ts",
"test:snapshot:record": "DSH_SNAPSHOT=record vitest run --config vitest.snapshot.config.ts --update",
"test:snapshot:refresh": "DSH_SNAPSHOT=refresh vitest run --config vitest.snapshot.config.ts"三种模式(来自 vitest.snapshot.config.ts):
| 模式 | 效果 | 并行? |
|---|---|---|
replay(默认) | 从录制响应启动真实子进程路径;比对汇编请求、归一化输出与持久化日志 golden。绝不写已提交输出。 | 并行 |
record | 调用真实 API(加载 .env 取密钥)并更新 fixture 与预期输出。 | 串行 |
refresh | 回放已提交脚本,只重写衍生的预期输出(无需密钥)。 | 串行 |
模型转录发生变化时用 record;回放输入仍有效、但预期输出变了时用 refresh。务必审阅每一处 JSONL 与预期输出 diff。refresh 刻意保持串行——并发写回会破坏从磁盘 fixture 采集 golden 的过程(--update 意味着写入)。Web 层在 CI 中强制 DSH_SNAPSHOT=replay(只读),确保一次提交绝不会悄悄改动预期输出。
源码平面解析
每个 Vitest 配置都把 vite-tsconfig-paths 指向 tsconfig.base.json(无 include → 匹配一切)。因此裸 workspace 导入解析到 src,绝不经包 exports 落到构建出的 lib/——那里的陈旧产物会加载第二份模块单例。构建产物只在 lib 模式子进程与构建产物冒烟测试(packages/examples/*/tests/built-bin.e2e.ts)中被显式消费。
共享的装饰器预变换(vitest.shared.ts 中的 standardDecoratorPlugin)在 Vite 解析前编译标准 TypeScript 装饰器;vitestExecArgv 传入 --no-webstorage,使进程级 Web Storage 不会遮蔽 jsdom 存储。
覆盖率:单文件 100%
vitest.config.ts 在 packages/*/*/src/** 上设置 thresholds: { perFile: true, statements: 100, branches: 100, functions: 100, lines: 100 }。未覆盖的一行通常是门禁正确地标记出来该删除的死代码——而非缺失的测试。coverage 的 exclude 列表豁免纯类型文件、自启动的 bin.ts/worker.ts 入口,以及一组带 TODO(gui) 债务标注的客户端/UI 文件。pwsh-local 文件仅在宿主无真实 pwsh 时豁免(通过 spawnSync(resolvePwshPath(), …) 探测)。
真实 API e2e 与 with-key 政策
docs/testing.md 明确阐述了该理念:“We are DeepSeek — do not ration real-API tests.”(我们就是 DeepSeek——不要吝啬真实 API 测试。)无密钥测试只证明管道通;只有带密钥的运行才能证明 agent 对真实模型真的有效。最高价值的是冒烟测试:启动真实示例、发一条提示、检查外部世界(外部状态),而不是模型的自述。每个 e2e 套件使用 DEEPSEEK_API_KEY 或各 providers 的密钥(EXA_API_KEY、PERPLEXITY_API_KEY,…),无密钥时自跳过,从而让无密钥 CI 与无密钥贡献者保持绿色。vitest.e2e.config.ts 设置 testTimeout: 120_000、retry: 2 与受限的工作池(DSH_E2E_MAX_WORKERS,默认 4)。
test-invariants setup 与 HMR 安全
主要配置都注册 setupFiles: ['./scripts/test-invariants.ts'](三个 web/浏览器配置不注册),且每个注册表测试都含 HMR 安全断言(dispose 贡献 fiber,断言清理)。不变量理念在运行时不变量页中有完整讨论。