测试覆盖率
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 参数,或者在 vitest.config.ts 文件里将 coverage.enabled 设置为 true :
{
"scripts": {
"test": "vitest",
"coverage": "vitest run --coverage"
}
}import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
coverage: {
enabled: true
},
},
})在覆盖率报告中设置需要统计或忽略的文件
你可以通过设置 coverage.include 和 coverage.exclude 来决定覆盖率报告中展示哪些文件。
Vitest 默认只统计测试中实际导入的文件。如果希望报告里也包含那些未被测试覆盖到的文件,需要在 coverage.include 中配置一个能匹配你源代码文件的模式:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
coverage: {
include: ['src/**/*.{ts,tsx}']
},
},
})├── src
│ ├── components
│ │ └── counter.tsx
│ ├── mock-data
│ │ ├── products.json
│ │ └── users.json
│ └── utils
│ ├── formatters.ts
│ ├── time.ts
│ └── users.ts
├── test
│ └── utils.test.ts
│
├── package.json
├── tsup.config.ts
└── vitest.config.ts如果你想从覆盖率中排除已经被 coverage.include 匹配到的部分文件,可以通过额外配置 coverage.exclude 来实现:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
coverage: {
include: ['src/**/*.{ts,tsx}'],
exclude: ['**/utils/users.ts']
},
},
})├── src
│ ├── components
│ │ └── counter.tsx
│ ├── mock-data
│ │ ├── products.json
│ │ └── users.json
│ └── utils
│ ├── formatters.ts
│ ├── time.ts
│ └── users.ts
├── test
│ └── utils.test.ts
│
├── package.json
├── tsup.config.ts
└── vitest.config.ts自定义代码覆盖率报告器
我们可以通过在 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请参阅类型定义查看有关详细信息。
代码忽略
两个覆盖率提供商都有自己的方法来忽略覆盖率报告中的代码:
使用 TypeScript 时,源代码使用 esbuild 进行转译,这会从源代码中删除所有注释(esbuild#516)。 被视为合法注释的注释将被保留。
你可以在忽略提示里加入 @preserve 关键字。 但要小心,这些忽略提示有可能会被打包进最终的生产环境构建中。
TIP
Follow https://github.com/vitest-dev/vitest/issues/2021 for updates about @preserve usage.
-/* istanbul ignore if */
+/* istanbul ignore if -- @preserve */
if (condition) {
-/* v8 ignore if */
+/* v8 ignore if -- @preserve */
if (condition) {示例
/* istanbul ignore start -- @preserve */
if (parameter) {
console.log('Ignored')
}
else {
console.log('Ignored')
}
/* istanbul ignore stop -- @preserve */
console.log('Included')
/* v8 ignore start -- @preserve */
if (parameter) {
console.log('Ignored')
}
else {
console.log('Ignored')
}
/* v8 ignore stop -- @preserve */
console.log('Included')/* v8 ignore if -- @preserve */
if (parameter) {
console.log('Ignored')
}
else {
console.log('Included')
}
/* v8 ignore else -- @preserve */
if (parameter) {
console.log('Included')
}
else {
console.log('Ignored')
} /* v8 ignore next -- @preserve */
console.log('Ignored')
console.log('Included')
/* v8 ignore next -- @preserve */
function ignored() {
console.log('all')
console.log('lines')
console.log('are')
console.log('ignored')
}
/* v8 ignore next -- @preserve */
class Ignored {
ignored() {}
alsoIgnored() {}
}
/* v8 ignore next -- @preserve */
condition
? console.log('ignored')
: console.log('also ignored') /* v8 ignore next -- @preserve */
try {
console.log('Ignored')
}
catch (error) {
console.log('Ignored')
}
try {
console.log('Included')
}
catch (error) {
/* v8 ignore next -- @preserve */
console.log('Ignored')
/* v8 ignore next -- @preserve */
console.log('Ignored')
}
// 由于 esbuild 不支持,需要使用 rolldown-vite。
// 参阅 https://vite.dev/guide/rolldown.html#how-to-try-rolldown
try {
console.log('Included')
}
catch (error) /* v8 ignore next */ {
console.log('Ignored')
} switch (type) {
case 1:
return 'Included'
/* v8 ignore next -- @preserve */
case 2:
return 'Ignored'
case 3:
return 'Included'
/* v8 ignore next -- @preserve */
default:
return 'Ignored'
}/* v8 ignore file -- @preserve */
export function ignored() {
return 'Whole file is ignored'
}覆盖率性能
如果你的项目中代码覆盖率生成较慢,请参阅 性能测试分析 | 代码覆盖率。
UI 模式
你可以在 UI 模式 和 HTML 报告器 中查看覆盖率报告。
此功能已与具有 HTML 输出的内置覆盖率报告器集成(html、html-spa 和 lcov 报告器)。html 报告器默认启用,开箱即用。若要与自定义报告器集成,可以配置 coverage.htmlDir。



## Coverage in Agent Environments When Vitest detects it is running inside an AI coding agent, it automatically adjusts the default text reporter to reduce output and minimize token usage:
skipFull: trueis set on thetextreporter, so files with 100% coverage are omitted from the terminal output.- The
text-summaryreporter is added automatically, so the agent always sees a concise totals table even whenskipFullhides all individual files.
These adjustments only apply when the text reporter is already part of the active reporter list (it is included in the default). Explicitly configured reporters are never removed.
