DeepSeek Harness 为什么要七份 vitest 配置:测试分层怎么切的

2026-08-16

deepseek-harness clone 下来,还没进 packages/,根目录先给你一排配置文件:五个 tsconfig*.json,七个 vitest*.ts。第一反应通常是「至于吗」。但如果你打算在这个仓库里改点东西,这排文件恰恰是你最先会撞上的东西——pnpm test 到底跑了哪一份、为什么你新写的 .stress.ts 一个都没被收进去、为什么 CI 说覆盖率不过而你本地绿的。

先把限定说在前面:截至我们采集的 2026-08-16,这个仓库建立于 2026-08-13,根 package.jsonversion0.1.0-rc.5,GitHub 上一个 Release 都没有,README 的 Developer preview 一节原文写着未来会出现破坏兼容性的变更。下面提到的文件名、脚本名、常量值都是这个快照(47f9438)里的实读结果,随时可能变。我们没有安装、没有构建、也没有跑过其中任何一条测试通道,所有关于「gate 会怎么判」的说法都是文档与脚本源码的转述。

先把数目数清楚

标题里说的「四份 tsconfig」需要一句校准:仓库根目录实际有五份。tsconfig.json 是 solution 文件,它自己不参与编译——文件头注释(tsconfig.json:2-8)明写 files: [] 要保持 program-less,并且 NEVER add include/files entries,它只 reference 另外两份聚合配置。真正承担编译形状的是四份:tsconfig.base.json(280 行)、tsconfig.base.client.json(11 行)、tsconfig.host.json(299 行)、tsconfig.client.json(99 行)。

vitest 侧七个文件同理,其中 vitest.shared.ts(43 行)不是 lane,而是被其它配置引用的共享件,它导出 vitestExecArgvvitest.shared.ts:9,按 --webstorage 是否可用决定要不要传 --no-webstorage)与 standardDecoratorPlugin():15)。所以准确说法是六条 lane 加一份共享件。

tsconfig:为什么必须切成 host 和 client 两半

这是整排配置里唯一一处「不这么切就不行」的地方,而且理由写在文件头。tsconfig.host.json:2-4 的注释说明 host 与 client 两侧会把 cordis 的 Context 合并到同一批 key 上,一个 program 没法同时看见两边。于是聚合被拆成 tsconfig.host.jsonnoEmit: true,见 :7)与 tsconfig.client.jsontypes: ["node"],见 :14;收 packages/client/*/tests 下的 .ts.tsx,见 :20-22)两份,各自建各自的 program,再由 solution 文件把两者 reference 起来。

两份 base 是形状来源。tsconfig.base.json 装的是全局严格开关:strict:19)、noUncheckedIndexedAccess:20)、exactOptionalPropertyTypes:21)、noImplicitOverride:22)、noFallthroughCasesInSwitch:23)、noUnusedLocals:24)、noUnusedParameters:25),加上 composite: true:12)、moduleResolution: bundler:8)、target: es2024:6)。:30 起是 paths,把每个 workspace 名映射到它的 srctsconfig.base.client.json 只负责客户端那点差异:jsx: react-jsxlib: ["ES2024","DOM","DOM.Iterable"]types: []:6-10)。

规模感可以用 project references 的条目数看:我们用 Python 数 "path" 出现的行数,tsconfig.host.json 有 187 行、tsconfig.client.json 有 49 行。注意这是引用条目行数,不是包数——这个仓库 packages/*/* 下有 219 个 workspace 成员,别把两个数字混着用。

这里有个容易被忽略的耦合:tsconfig.base.json 的文件头注释(:2-4)说它同时兼任 vite-tsconfig-paths 的解析门面,vitest 配置就指向它。也就是说 paths 改一次,类型检查和测试解析是一起动的,不是两件事。

vitest:六条 lane 是按「花什么代价」切的

把六条 lane 排一遍,各自的定位取自它们自己的文件头注释:

配置文件行数文件头自述的定位
vitest.config.ts286默认单测 lane,自带 scripts/coverage-uncovered-locations.cjs 报告器,打印每条未覆盖语句的 path:line:col:9-13
vitest.e2e.config.ts59真实 API lane,注释写它单列是因为「spends tokens」;无凭据自跳过(:5-8
vitest.snapshot.config.ts69快照 lane
vitest.web.config.ts36Web 浏览器 lane
vitest.web.perf.config.ts16手动高基数诊断,注释写它在 CI web gate 之外(:4-5
vitest.web-stress.config.ts16JSDoc 原文:Opt-in browser performance lane; no default Vitest config includes *.stress.ts.:5

切分的轴不是「测什么」,而是「跑一次要付出什么」。e2e 花的是 token,所以它必须能在没凭据时自己跳过;web lane 需要浏览器;perf 与 stress 需要长时间且结论不稳定,所以干脆从默认 include 里拿掉——vitest.web-stress.config.ts 那句 JSDoc 说得很直白,没有任何默认配置会收 *.stress.ts,它的 include 只有 apps/web/stress-tests/**/*.stress.ts,并且 fileParallelism: false:13)。这也解释了开头那个现象:你新建一个 .stress.ts 然后 pnpm test,它当然一个都不跑,因为设计上就不该被默认收进去。

几个写死在配置里的常量顺带记一下:vitest.e2e.config.ts:16DEFAULT_E2E_MAX_WORKERS = 4vitest.snapshot.config.ts:6DEFAULT_SNAPSHOT_MAX_CONCURRENCY = 5vitest.web.perf.config.ts:10-13hookTimeout: 180_000testTimeout: 600_000(include 是 apps/web/tests/**/*.perf.ts)。这些是配置里的默认值,不是「你跑起来会花多久」的承诺,也不能拿来推算任何执行时间或资源开销——我们没跑过其中任何一条。

vitest.web.config.ts:6-8 还记了一条环境约定:Linux 上的 PR CI 把 DSH_SNAPSHOT 钉成 replay,比对已提交的 golden 文件,record 与 refresh 只作为本地显式工作流。这意味着「本地重录一遍快照」和「CI 判定」是两个动作,别指望在 CI 上顺手刷新。

lane 的实际重量:按文件后缀数一遍

配置文件本身看不出各条 lane 有多重。按文件名后缀在这份快照里数:*.spec.ts 692 个、*.e2e.ts 129 个、*.snapshot.ts 19 个、*.stress.ts 1 个、*.perf.ts 1 个。

也就是说这套七文件的分层里,绝大部分重量压在默认 lane 上,末两条 lane 各自只有一个文件。为一个文件单开一份配置看着奢侈,但反过来想,它们各自的 include 与超时设定跟默认 lane 完全不兼容,混在一起才是麻烦。

入口在 npm scripts,判据在 AGENTS.md

这套分层对外的唯一入口是根 package.json 的 scripts:test:34)、test:coverage:35)、test:e2e:36)、test:snapshot:38)、test:snapshot:record:39DSH_SNAPSHOT=record)、test:snapshot:refresh:40)、test:web:42)、test:web:built:44)、test:web:perf:45)、test:web:stress:47)、test:gui:48)。整个根 package.json 有 123 条 scripts,其中 test 开头的 14 条、verify- 开头的 37 条、check 开头的 14 条——测试 lane 只是这套 gate 体系的一部分。

有一条最容易踩:AGENTS.md:92 明写 CI 的覆盖率 gate 是 test:coverage 而不是 testAGENTS.md:65 进一步写这条 gate 的口径是对 packages/*/*/src 的 per-file 100%。本地跑 pnpm test 全绿、CI 仍然拦下,这两句就是原因所在。默认 lane 那个自带报告器会打印每条未覆盖语句的 path:line:col,正是为这条 gate 服务的。

顺带提醒,pre-push 钩子里只挂了一件事:lefthook.yml:52-55pnpm run typecheck。测试不在 push 前跑。

想自己核一遍,动作是这样的

  • 想知道某个测试文件归哪条 lane:直接看它的后缀,再回对应 vitest.*.config.tsinclude。后缀对不上任何一条,就是没有 lane 会收它。
  • 想知道类型错误来自哪一侧:看报错文件在 packages/client/* 还是别处,分别对应 tsconfig.client.jsontsconfig.host.json 两个 program。
  • 想改 paths:记住 tsconfig.base.json 同时被 vitest 侧引用,改完两边都要过。
  • 在新克隆的仓库里跑之前:apps/cli/reference/README.md:84 写明 pnpm run build 必须在新克隆后单独跑一次,缺 Typert host 产物会以模块解析错误让 profile boot 失败且不给构建提示;同一处还写 launcher 不检查产物新鲜度,陈旧的 bundle 会照跑不误。

Windows 侧只能说到这里

本站读者用 Windows 的多,所以这条要写清楚:我们在仓库里能核到的 Windows 相关线索只有几处——根 package.json:58check:windows-wine:59-61 的三条 check:ci:windows-*.github/workflows/ci.yml:661runs-on: [self-hosted, dsh-win-ci, windows]、以及 apps/cli/tests/windows-shell.spec.ts 这个文件的存在。至于「六条 lane 里哪几条能在 Windows 上跑通」,我们没能在仓库里找到一处完整清单,所以这里不给结论。

另外值得记一笔的是这个仓库的 Node 门槛只在根 package.json:8-10 声明了一处("node": "^22.19.0 || >=24.0.0"),包管理器钉在 package.json:7pnpm@11.7.0;而我们扫过的 230 份 packages/*/*apps/*vendor/* manifest 里,没有任何一份带 engines 字段,根包自己又是 private: truepackage.json:5)。这两处只是并列陈述的实读结果,我们不推断原因。

值得抄走的是切分轴

七个 vitest 文件、五个 tsconfig 文件,摊开看是十二份配置,但真正的信息量只有两句:类型侧按 program 能不能共存来切,测试侧按每条 lane 花什么代价来切。前者是硬约束(cordis Context 的 key 合并),后者是取舍(token、浏览器、时长、稳定性各自单列)。

至于这种切法在别的项目里合不合适,我们没有依据评价——这份仓库的规模是 packages/*/* 下 219 个 workspace 成员、.ts.tsx 合计 2578 个文件 566,700 行(截至 2026-08-16 的实读值),换一个体量结论未必成立。能确定的只有一点:在这个仓库里,配置文件的数量不是随手加出来的,每一份的文件头都写了它为什么单独存在,读之前先读注释比猜要快得多。

延伸阅读


本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的 架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-16,对应仓库快照 47f9438(版本 0.1.0-rc.5)。 本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目, 因此不涉及界面外观、操作手感与运行速度的任何描述。 该仓库建立于 2026-08-13,README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更, 文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。