Supabase 单仓中的 Vitest 代码覆盖率实战:V8 与 Istanbul 双 Provider 配置全解
本文基于 Supabase 仓库中 Vitest 技能参考文档 features-coverage.md 整理扩充,系统讲解 Vitest 代码覆盖率(Code Coverage)的启用方式、完整配置项、V8/Istanbul 双 Provider 选型、阈值与忽略语法,并结合本仓库中 apps/studio、packages/ui、packages/dev-tools 等真实配置,给出可直接落地的覆盖率工程实践。
1. 启用覆盖率的两种方式
启用覆盖率最简单的途径是在命令行追加 --coverage 标志:
# Run tests with coverage
vitest run --coverage
也可以在配置文件中显式声明 enabled: true,两种方式效果等价,后续「要点」小节会再总结。值得注意的是,Vitest 的覆盖率不是内置功能,而是由独立包提供的 provider 插件承担,因此还需要安装对应依赖(见第 4 节)。
2. 完整配置示例逐项解析
覆盖率配置位于 Vitest 配置的 test.coverage 字段下。以下是参考文档给出的完整配置骨架,每一项都附带了用途说明:
// vitest.config.ts
defineConfig({
test: {
coverage: {
// Provider: 'v8' (default, faster) or 'istanbul' (more compatible)
provider: 'v8',
// Enable coverage
enabled: true,
// Reporters
reporter: ['text', 'json', 'html'],
// Files to include
include: ['src/**/*.{ts,tsx}'],
// Files to exclude
exclude: [
'node_modules/',
'tests/',
'**/*.d.ts',
'**/*.test.ts',
],
// Report uncovered files
all: true,
// Thresholds
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80,
},
},
},
})
各字段含义:
| 字段 | 作用 |
|---|---|
provider |
覆盖率引擎,'v8'(默认,更快)或 'istanbul'(兼容性更好) |
enabled |
是否在运行测试时收集覆盖率,等价于命令行 --coverage |
reporter |
报告输出格式,可多选(第 5 节详述) |
include |
纳入统计的文件 glob 模式 |
exclude |
排除的文件,通常排除 node_modules/、测试目录、声明文件与测试文件 |
all: true |
把「从未被任何测试加载」的文件也计入报告,避免覆盖率虚高 |
thresholds |
覆盖率下限,低于阈值则测试运行以失败退出(第 6 节详述) |
3. V8 与 Istanbul 两种 Provider 的选型
3.1 V8(默认推荐)
npm i -D @vitest/coverage-v8
- 更快,无需预置(pre-instrumentation)插桩;
- 直接使用 V8 引擎原生的覆盖率数据;
- 对大多数项目是推荐选择。
3.2 Istanbul
npm i -D @vitest/coverage-istanbul
- 通过预插桩(pre-instruments)代码收集覆盖率;
- 可以在任意 JS 运行时中工作;
- 开销更大,但兼容性更广。
选型逻辑很直接:在 Node/V8 环境下优先用 V8 换取速度;只有当运行环境特殊、或需要 Istanbul 生态的插桩语义时才切换到 Istanbul。本仓库所有使用覆盖率的包统一选择 V8——从各 package.json 的依赖声明可以看到,apps/studio、packages/ui、packages/ui-patterns、packages/common、packages/pg-meta 等均在 devDependencies 中以 catalog: 方式引用 @vitest/coverage-v8(如 apps/studio/package.json),由 pnpm workspace 的 catalog 机制统一管理版本,仓库中没有任何包安装 @vitest/coverage-istanbul。
4. Reporter 选择与报告目录
coverage: {
reporter: [
'text', // Terminal output
'text-summary', // Summary only
'json', // JSON file
'html', // HTML report
'lcov', // For CI tools
'cobertura', // XML format
],
reportsDirectory: './coverage',
}
各 reporter 的典型用途:
text/text-summary:终端输出,本地开发即时查看;json/html:本地与 UI 查看(html同时是 Vitest UI 展示覆盖率的前提,见第 8 节);lcov:产出lcov.info,供 CI 平台的覆盖率工具消费;cobertura:XML 格式,供另一类 CI 集成消费。
reportsDirectory 控制报告落盘位置,默认 ./coverage,也就是后面 CI 示例里 ./coverage/lcov.info 路径的来源。
5. Thresholds:用阈值强制执行覆盖率底线
当覆盖率低于阈值时让测试直接失败:
coverage: {
thresholds: {
// Global thresholds
lines: 80,
functions: 75,
branches: 70,
statements: 80,
// Per-file thresholds
perFile: true,
// Auto-update thresholds (for gradual improvement)
autoUpdate: true,
},
}
要点:
- 四个维度(
lines、functions、branches、statements)可以各自设置不同下限,例如分支覆盖最难达标,可以单独放宽; perFile: true把阈值从「全仓总量」收紧到「每个文件都要达标」,防止个别文件零覆盖被整体均值掩盖;autoUpdate: true用于渐进式提升场景:阈值随实际覆盖率自动更新,避免存量项目因历史欠账而无法直接启用硬性阈值。
6. 忽略特定代码:v8 ignore 与 istanbul ignore
对确实无法测试(或不值得测试)的代码,用注释指令让 provider 跳过统计:
6.1 V8 语法
/* v8 ignore next -- @preserve */
function ignored() {
return 'not covered'
}
/* v8 ignore start -- @preserve */
// All code here ignored
/* v8 ignore stop -- @preserve */
ignore next 忽略下一条语句,ignore start/stop 忽略代码块。
6.2 Istanbul 语法
/* istanbul ignore next -- @preserve */
function ignored() {}
/* istanbul ignore if -- @preserve */
if (condition) {
// ignored
}
ignore if 可以只忽略分支本身而保留分支体,粒度更细。
关键细节:注释中的 @preserve 标志用于让这些注释在 esbuild 压缩/转译时不被剥离(@preserve keeps comments through esbuild)。如果漏写,覆盖率忽略指令在构建产物里会失效,统计结果出现“莫名其妙”的未覆盖行。
7. package.json 脚本惯例与 Vitest UI
7.1 脚本约定
{
"scripts": {
"test": "vitest",
"test:coverage": "vitest run --coverage",
"test:coverage:watch": "vitest --coverage"
}
}
vitest run --coverage(一次性)与 vitest --coverage(watch 模式)的区别在于是否常驻监听。本仓库各包实际采用的脚本正是这一模式,且普遍额外提供了一条查看 HTML 报告的命令:
- apps/studio/package.json:
"test": "vitest --run --coverage"、"test:ci": "vitest --run --coverage"、"test:report": "open coverage/lcov-report/index.html"; - packages/ui/package.json:
"test:ci": "vitest --run --coverage"加同样的test:report; - packages/ui-patterns/package.json:
"test:coverage": "vitest --run --coverage"; - packages/pg-meta/package.json:
"test": "test/run-tests.sh vitest run --coverage",把覆盖率跑在包内test/run-tests.sh脚本之上(该脚本负责数据库等前置环境)。
可以看到仓库的 CI 测试命令(test:ci)本身就带 --coverage,即覆盖率是常态而非可选步骤;本地则通过 test:report 打开 coverage/lcov-report/index.html 查看 lcov 生成的 HTML 报告。
7.2 在 Vitest UI 中可视化覆盖率
coverage: {
enabled: true,
reporter: ['text', 'html'],
}
然后运行 vitest --ui,即可在 UI 里按文件查看行级覆盖情况。前提是 reporter 中包含 html。
8. CI 集成与分片合并
8.1 CI 上传覆盖率
典型的 CI 集成是「带覆盖率跑测试 + 上传 lcov 产物」两步:
# GitHub Actions
- name: Run tests with coverage
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
这里要求 reporter 包含 lcov,使 ./coverage/lcov.info 存在——这正是仓库各包把 lcov 放进 reporter 数组的原因。
8.2 分片(sharding)下的覆盖率合并
在 CI 中按 shard 拆分测试提速时,每个分片各自产出覆盖率,需要 --reporter=blob 记录中间产物,再统一合并:
vitest run --shard=1/3 --coverage --reporter=blob
vitest run --shard=2/3 --coverage --reporter=blob
vitest run --shard=3/3 --coverage --reporter=blob
vitest --merge-reports --coverage --reporter=json
前三个命令分别跑三个分片并落 blob 报告,最后一个命令用 --merge-reports 把所有分片的覆盖率合并成最终报告。
9. 源码佐证:本仓库的真实覆盖率配置
参考文档给出的是通用骨架,本仓库的四个 Vitest 配置展示了它在不同包里的实际裁剪方式。
9.1 studio:面向 lib/ 的定向统计
apps/studio/vitest.config.ts 第 44–52 行:
coverage: {
reporter: ['text', 'text-summary', 'lcov'],
exclude: [
'**/*.test.ts',
'**/*.test.tsx',
'**/base64url.ts', // [Jordi] Tests for this file exist in ... so we can ignore.
],
include: ['lib/**/*.ts'],
},
从这份配置可以读出三个工程决策:
include: ['lib/**/*.ts']把统计范围收窄到lib/工具库目录,而不是整个巨型前端应用(studio 的components/、routes/目录文件数以千计,全量统计既不现实也无必要);reporter同时输出text、text-summary、lcov,分别服务终端速览、摘要与 CI 上传,与package.json里test:ci带--coverage的做法配套;exclude中显式排除了base64url.ts,并在注释中说明理由——该文件的测试存在于社区独立仓库,属于典型的「用排除 + 注释留痕」处理外部覆盖来源的做法,与第 6 节的 ignore 注释是同类手段。
另外该配置从 vitest/config 引入 configDefaults 并把它并入测试文件的 exclude(第 5、37–42 行),同时按 process.env.CI 在 CI 上对 flaky 测试 retry: 2——这说明覆盖率运行发生在与常规测试同一条链路上,而不是独立流水线。
9.2 组件库包:src 全量 + lcov
packages/ui/vitest.config.ts 与 packages/dev-tools/vitest.config.ts 配置高度一致:
coverage: {
reporter: ['lcov'],
exclude: ['**/*.test.ts', '**/*.test.tsx'],
include: ['src/**/*.ts', 'src/**/*.tsx'], // dev-tools 为 ['**/*.ts', '**/*.tsx']
},
作为组件库,packages/ui 把 include 精确到 src/ 下的 TS/TSX,本地只产出 lcov 供 CI 消费,不做终端 text 输出。packages/dev-tools/vitest.config.ts 则对整个包目录(**/*.ts / **/*.tsx)统计,因为该包源码规模小、包内即 src 平铺结构。
9.3 ui-patterns:text + json + html 三件套
packages/ui-patterns/vitest.config.ts:
coverage: {
reporter: ['text', 'json', 'html'],
},
这是文档默认 reporter 组合的直译:终端看 text、脚本消费 json、浏览器看 html。对照第 4 节可知,html 报告落盘到 coverage/ 后即可通过 open coverage/lcov-report/index.html 一类命令(见 packages/ui/package.json 的 test:report)本地查阅。
9.4 小结:本仓库的覆盖率实践模式
从上述源码看,本仓库形成了一套一致的覆盖率实践:
- Provider 统一 V8:全部通过 pnpm workspace catalog 引入
@vitest/coverage-v8,版本集中管理; - include 按包裁剪:应用型包只统计核心目录(
lib/**),库型包统计src/**,避免巨型应用的全量统计; - exclude 统一排除测试文件:
**/*.test.ts/**/*.test.tsx是所有配置的公共排除项; - CI 命令内置
--coverage:test/test:ci脚本自带覆盖率,本地另有test:report打开 HTML 报告; - 无 thresholds 硬门槛:从源码结构看,当前各包均未在配置中启用
thresholds或perFile,覆盖率以「可观测、可上传」为主要目标,阈值约束留待团队按基线另行设定——这也是参考文档中autoUpdate渐进式方案的适用场景。
10. 要点回顾
- V8 更快(原生覆盖率、无预插桩),Istanbul 兼容性更广(预插桩、任意 JS 运行时);
- 用
--coverage标志或coverage.enabled: true启用; include/exclude决定统计口径,all: true让从未被加载的文件也出现在报告中;thresholds(含perFile、autoUpdate)用于强制执行最低覆盖率并支持渐进式提升;- 忽略注释必须带
-- @preserve,否则 esbuild 会把注释连同忽略指令一起删掉; - CI 场景用
lcovreporter 产出./coverage/lcov.info上传;分片运行则用--reporter=blob+--merge-reports合并覆盖率; - 本仓库的参考实现见 apps/studio/vitest.config.ts、packages/ui/vitest.config.ts、packages/dev-tools/vitest.config.ts、packages/ui-patterns/vitest.config.ts,配套脚本见各包的
package.json。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00