首页 / DeepSeek Harness 入门教程 / 测试与扩展开发

DeepSeek Harness 入门教程

测试与扩展开发

本教程共 32 篇 · 第 30 篇 · 更新于 2026-08-15 · 约 7 分钟阅读

测试快照invariants开发环境cookbook贡献指南

本节目标:搞懂 dsh 仓库「怎么保证质量」——四层测试怎么分工、invariants 与快照/重放是什么思路,以及想动手扩展时从哪些文档和命令入手。

想深入一个开源项目,先读它的测试策略和开发指南。dsh 官方文档里 testing.mddevelopment.md 就是这两份地图。这一章把它们翻译成人话。

测试四层:每层抓不同的盲区

仓库测试分四层,命令和职责如下:

层级命令抓什么
单元测试pnpm run testvitest 跑包内 tests/**,优先边界、错误路径、事件顺序、并发竞态
覆盖率门禁pnpm run test:coveragepackages/*/*/src 按文件 100% 行覆盖
真实 API e2epnpm run test:e2e带密钥调用真实提供方 API(DeepSeek 等),缺密钥自动跳过
快照pnpm run test:snapshot / test:web无密钥预期输出固定对外行为;浏览器快照用 Chromium 回放比较

关键认知:覆盖率是必要条件,永远不是充分条件。它证明行被执行过,不证明功能按交付预期工作。0001 事故里 100% 行覆盖率照样放过两个集成 bug,就是这个道理的注脚。

三个核心思路:invariants、快照、重放

运行时不变量(invariants)。 ctx.invariants 是一个由包自己拥有的检查注册表:每个包通过独立的 ./invariant 文件向 InvariantService 登记检查,失败时带明确的包归属。它检查的是「组合事实」——比如「模型可见的输入必须能从日志重建」这类架构约束,由运行时断言兜底,而不是只写在文档里。

快照(snapshot)。 快照用无密钥的预期输出固定对外行为:ACP 场景回放录制会话,对归一化 JSON-RPC 与重新持久化的日志做 diff;headless 场景通过 JSONL 测试驱动;浏览器快照把回放后的渲染与 apps/web/tests/snapshots/ 比较。模型 transcript 变化时用 pnpm run test:snapshot:record 重录,回放输入仍有效时用 test:snapshot:refresh

重放(replay)。 会话日志是事件流(session/event → JSONL),重放就是 sessions.create(id, { seed })。恢复测试按步骤区分分片前/分片后的失败,证明失败分片不会派生出消息或工具副作用。崩溃恢复、fork、遥测都派生自这条事件流——所以官方有一条硬规则:模型可见即已记录,抵达模型请求的一切都必须能从日志重建。

Warning

快照刷新是 fixture 的生产过程,不是正确性审查。每处 diff 都要人工评审;0002 事故就是快照把 UNKNOWN_TOOL 结果当成新预期接受下来的。

三条测试铁律

1. 测试真实入口路径。 手动构建的 ctx.plugin(...) 套件不够。产品可见的插件必须通过 Loader 和真实 cordis.yml 启动,只 mock 外部服务或非确定性输入。发布的产物跑的是构建后的 lib/bin.js 加普通 node,从而暴露 tsx 会掩盖的失败。

2. 验证外部世界,而非自我报告。 e2e 断言应重新运行命令或从外部重新读文件;对 agent 自身输出做关键词探测,会让作弊的 agent 通过。断言未修改的文件逐字节一致。

3. mock 只用于高开销或不确定的边界。 只 mock LLM 适配器、网络、时钟;下游一切保持真实。手写替身只能证明桥接层在搬运字节,不能证明交付的工具行为符合断言。

开发环境搭建

想自己编译、测试、改源码,按官方 development.md 来:

前置条件:

- Node.js 22.19+ 或 24+(CI 覆盖 22.19 / 24 / 26)
- pnpm(仓库固定 pnpm@11.7.0;pnpm --version 解析不了就先 corepack enable)
- Git 2.26+(钩子会启用 worktree 专属配置扩展)
- 可选:DeepSeek API key(真实 API e2e 与演示用)

首次搭建:

git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run typecheck   # 成功退出即表示搭建完成
Tip

依赖从缓存恢复或 postinstall 被跳过导致钩子缺失时,手动补一次:node scripts/install-lefthook.mjs。移动检出目录后也要重跑。

日常命令速查:

pnpm run build        # 依赖构建产物的检查前先构建
pnpm run check:all    # 全面本地门禁集(独立于 Git 钩子)
pnpm run test         # 单元测试
pnpm run test:coverage
pnpm run test:e2e     # 需要 DEEPSEEK_API_KEY
pnpm run test:snapshot

真实密钥放仓库根目录被 gitignore 的 .env 文件:

DEEPSEEK_API_KEY=***
DEEPSEEK_BASE_URL=https://...   # 可选,默认公开 API

cookbook 扩展食谱导航

官方 cookbook 把「加什么功能」对应到「哪篇食谱」:

想做什么读哪篇
加一个工具adding-a-tool.md(工具定义的真源)
加一个 LLM 适配器adding-an-llm-adapter.md
加一个 Chat 节点/UI 业务行adding-a-conversation-node.md
加一个 workspace 包adding-a-package.md(逐文件清单)
功能→机制映射总表extension-cookbook.md

extension-cookbook 里最值钱的是那张「产品功能 → 插件机制」映射表:钩子系统挂在 agent/* + tools/* 事件上、上下文压缩用 ctx.compaction seam、subagent 委派用 ctx.subagents 注册表、定时任务用「定时器触发 → 空闲时 followup / 忙碌时 inject」……每一项产品功能都映射到已文档化扩展点上的监听器,没有一行修改循环本身——这就是「无特权内核」声明可验证的原因。

加包时注意 adding-a-package.md 的检查清单:包名用符合实际角色的词(Registry/Runtime/Provider 各有严格定义,不按 Cordis 基类命名),package.json 有一组 invariant(private: truemain: "lib/index.js"@deepseek-ai/cordis 同时出现在 peer 与 devDependencies 等),由 pnpm run constraints 强制执行。

PR 审查与工程纪律

仓库的审查文化值得一提:maintaining-dsh-code-review.md 描述了一个半自动化的 PR 反馈闭环——维护工具收集合并前的人工评审反馈,比对反馈时与最终落地的 patch,用双适配器分类「是否被采纳」,再小周期更新审查 skill。规则是:根据 diff 本身作判断,不因为「评审者已批准」就直接接受

贡献代码时还有三条纪律:

  • 用 TODO 标记已知问题,按紧急度分级:FIXME(发布阻塞)、TODO(尽快修)、XXX(有空再说)。
  • lefthook 钩子负责本地快检(pre-commit 校验配对记录与暂存文件,pre-push 跑 typecheck);CI 负责全量门禁与 Node 兼容矩阵。
  • 文档改动用 pnpm run doc-sync;改公开行为还需更新所属 README;双语文档由配对合并驱动维护。
Note

版本基线 @deepseek-ai/dsh 0.1.0-rc.6 处于 Developer Preview。clone 源码后以仓库实际状态为准,命令与包名可能已经变化。

小结

测试四层各有盲区,invariants + 快照 + 重放是质量底座;开发环境一条 pnpm install && pnpm run typecheck 就能站起来;扩展开发先查 cookbook 映射表,再按包清单动手。到这一章,dsh 从使用到架构、从防御到测试的完整拼图就齐了。