测试覆盖率
Vitest 通过 v8 支持原生代码覆盖率,通过 istanbul 支持检测代码覆盖率。
测试覆盖率提供者
v8 和 istanbul 的支持都是可选的。 默认情况下,启用 v8。
你可以通过将 test.coverage.provider 设置为 v8 或 istanbul 来选择覆盖工具:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
coverage: {
provider: 'v8' // or 'istanbul'
},
},
})当你启动 Vitest 进程时,它会提示你自动安装相应的支持包。
或者,如果你更喜欢手动安装它们:
npm i -D @vitest/coverage-v8npm i -D @vitest/coverage-istanbulV8 Provider
INFO
以下对 V8 覆盖率的说明仅适用于 Vitest,并不适用于其他测试工具。 从 v3.2.0 版本开始,Vitest 在 V8 覆盖率中采用了 基于 AST 的重映射技术 ,从而生成与 Istanbul 一致的覆盖率报告。
这让用户在享受 V8 覆盖率高速执行的同时,也能获得 Istanbul 覆盖率的高准确度。
Vitest 默认采用 v8 作为覆盖率提供器。 此提供器依赖于基于 V8 引擎 的 JavaScript 运行环境,比如 NodeJS、Deno,或者 Google Chrome 等 Chromium 内核的浏览器。
覆盖率收集是在程序运行时完成的,通过 node:inspector 模块以及浏览器中的 Chrome DevTools Protocol 协议 与 V8 交互即可实现。这样,用户的源码可以直接被执行,而不需要事先进行插桩处理。
- ✅ 推荐使用该选项
- ✅ 不需要先做转译处理,测试文件可直接运行
- ✅ 执行速度比 Istanbul 更快
- ✅ 占用内存比 Istanbul 更少
- ✅ 覆盖率报告的精确度与 Istanbul 相当(自 Vitest v3.2.0 起)
- ⚠️ 在某些场景下(如加载大量模块)可能比 Istanbul 慢,因为 V8 不支持只对特定模块收集覆盖率
- ⚠️ 存在 V8 引擎自身的一些小限制,详见
ast-v8-to-istanbul的限制说明 - ❌ 不支持非 V8 环境,比如 Firefox、Bun;也不适用于不通过 profiler 提供 V8 覆盖率的环境,例如 Cloudflare Workers
Istanbul 覆盖率提供方案
Istanbul 代码覆盖率工具 自 2012 年发布以来,已在各种场景中得到了充分验证。 这种覆盖率提供器能在任何 JavaScript 运行环境中使用,因为它是通过在用户源码中插入额外的代码来跟踪执行情况。
简单来说,插桩就是在你的源文件里加入一段额外的 JavaScript,用于记录代码的执行路径:
// 分支和函数覆盖率计数器的简化示例
const coverage = {
branches: { 1: [0, 0] },
functions: { 1: 0 },
}
export function getUsername(id) {
// 当这个函数被调用时,函数覆盖率会增加
coverage.functions['1']++
if (id == null) {
// 当这个分支被调用时,分支覆盖率会增加
coverage.branches['1'][0]++
throw new Error('User ID is required')
}
// 当 if 语句条件不满足时,隐式的 else 覆盖率会增加
coverage.branches['1'][1]++
return database.getUser(id)
}
globalThis.__VITEST_COVERAGE__ ||= {}
globalThis.__VITEST_COVERAGE__[filename] = coverage - ✅ 可以在任何 JavaScript 环境中使用
- ✅ 已被业界广泛采用并在 13 年中得到充分验证
- ✅ 某些情况下执行速度优于 V8,因为插桩可以只针对特定文件,而 V8 会对所有模块插桩
- ❌ 需要在执行前进行插桩处理
- ❌ 由于插桩带来的额外开销,执行速度普遍比 V8 慢
- ❌ 插桩会使文件体积变大
- ❌ 内存消耗比 V8 更高
覆盖率配置指南
TIP
建议始终在配置文件中定义 coverage.include。 这有助于 Vitest 减少 coverage.all 选择的文件数量。
要在启用的情况下进行测试,你可以在 CLI 中传递 --coverage 标志。 默认情况下, 将使用 ['text', 'html', 'clover', 'json'] 作为测试报告器。
{
"scripts": {
"test": "vitest",
"coverage": "vitest run --coverage"
}
}要对其进行配置,需要在配置文件中设置 test.coverage 选项:
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
coverage: {
reporter: ['text', 'json', 'html'],
},
},
})自定义覆盖率的报告器
我们可以通过在 test.coverage.reporter 中传递软件包名称或绝对路径来使用自定义覆盖报告器:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
coverage: {
reporter: [
// 使用 NPM 包的名称指定报告器
['@vitest/custom-coverage-reporter', { someOption: true }],
// 使用本地路径指定报告器
'/absolute/path/to/custom-reporter.cjs',
],
},
},
})自定义报告器由 Istanbul 加载,必须与其报告器接口相匹配。查看 built-in reporters' implementation 了解更多详情。
const { ReportBase } = require('istanbul-lib-report')
module.exports = class CustomReporter extends ReportBase {
constructor(opts) {
super()
// 从配置中传递的选项在这里可用
this.file = opts.file
}
onStart(root, context) {
this.contentWriter = context.writer.writeFile(this.file)
this.contentWriter.println('Start of custom coverage report')
}
onEnd() {
this.contentWriter.println('End of custom coverage report')
this.contentWriter.close()
}
}自定义覆盖率的提供者
也可以通过将 'custom' 传递给 test.coverage.provider 来配置你的自定义覆盖率提供者:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
coverage: {
provider: 'custom',
customProviderModule: 'my-custom-coverage-provider',
},
},
})自定义覆盖率提供者需要一个 customProviderModule 选项,它是一个模块名称或从中加载 CoverageProviderModule 的路径。 它必须将实现 CoverageProviderModule 的对象导出为默认导出:
import type {
CoverageProvider,
CoverageProviderModule,
ResolvedCoverageOptions,
Vitest,
} from 'vitest'
const CustomCoverageProviderModule: CoverageProviderModule = {
getProvider(): CoverageProvider {
return new CustomCoverageProvider()
},
// 实现 CoverageProviderModule 的其余部分...
}
class CustomCoverageProvider implements CoverageProvider {
name = 'custom-coverage-provider'
options!: ResolvedCoverageOptions
initialize(ctx: Vitest) {
this.options = ctx.config.coverage
}
// 实现 CoverageProvider 的其余部分...
}
export default CustomCoverageProviderModule请参阅类型定义查看有关详细信息。
更改默认覆盖率报告文件夹位置
运行覆盖率报告时,会在项目的根目录中创建一个 coverage 文件夹。 如果你想将它移动到不同的目录,请使用 vite.config.js 文件中的 test.coverage.reportsDirectory 属性。
import { defineConfig } from 'vite'
export default defineConfig({
test: {
coverage: {
reportsDirectory: './tests/unit/coverage',
},
},
})代码忽略
两个覆盖率提供商都有自己的方法来忽略覆盖率报告中的代码:
使用 TypeScript 时,源码使用 esbuild 进行转译,这会从源码中删除所有注释(esbuild#516)。 被视为合法注释的注释将被保留。
你可以在忽略提示里加入 @preserve 关键字。 但要小心,这些忽略提示有可能会被打包进最终的生产环境构建中。
-/* istanbul ignore if */
+/* istanbul ignore if -- @preserve */
if (condition) {
-/* v8 ignore if */
+/* v8 ignore if -- @preserve */
if (condition) {其他选项
要查看有关覆盖率的所有可配置选项,请参见 覆盖率配置参考。
覆盖率性能
如果你的项目中代码覆盖率生成较慢,请参阅 性能测试分析 | 代码覆盖率。
UI 模式
我们可以在 UI 模式 中查看你的覆盖率报告。
UI 模式 会在以下情况下启用覆盖率报告:
- 显式启用覆盖率报告:在配置文件中设置
coverage.enabled=true,或运行 Vitest 时添加--coverage.enabled=true标志。 - 添加 HTML 报告器:将
html添加到coverage.reporter列表中,我们还可以启用subdir选项,将覆盖率报告放在子目录中。



