本文档适用于 Vitest v3 (旧版本)。如需查看最新版本请访问 https://cn.vitest.dev.

Skip to content

测试覆盖率

Vitest 通过 v8 支持原生代码覆盖率,通过 istanbul 支持检测代码覆盖率。

测试覆盖率提供者

v8istanbul 的支持都是可选的。 默认情况下,启用 v8

你可以通过将 test.coverage.provider 设置为 v8istanbul 来选择覆盖工具:

vitest.config.ts
ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    coverage: {
      provider: 'v8' // or 'istanbul'
    },
  },
})

当你启动 Vitest 进程时,它会提示你自动安装相应的支持包。

或者,如果你更喜欢手动安装它们:

bash
npm i -D @vitest/coverage-v8
bash
npm i -D @vitest/coverage-istanbul

V8 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
Test fileEnable V8 runtime coverage collectionRun fileCollect coverage results from V8Remap coverage results to source filesCoverage report

Istanbul 覆盖率提供方案

Istanbul 代码覆盖率工具 自 2012 年发布以来,已在各种场景中得到了充分验证。 这种覆盖率提供器能在任何 JavaScript 运行环境中使用,因为它是通过在用户源码中插入额外的代码来跟踪执行情况。

简单来说,插桩就是在你的源文件里加入一段额外的 JavaScript,用于记录代码的执行路径:

js
// 分支和函数覆盖率计数器的简化示例
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 更高
Test filePre‑instrumentation with BabelRun fileCollect coverage results from Javascript scopeRemap coverage results to source filesCoverage report

覆盖率配置指南

TIP

建议始终在配置文件中定义 coverage.include。 这有助于 Vitest 减少 coverage.all 选择的文件数量。

要在启用的情况下进行测试,你可以在 CLI 中传递 --coverage 标志。 默认情况下, 将使用 ['text', 'html', 'clover', 'json'] 作为测试报告器。

json
{
  "scripts": {
    "test": "vitest",
    "coverage": "vitest run --coverage"
  }
}

要对其进行配置,需要在配置文件中设置 test.coverage 选项:

ts
// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    coverage: {
      reporter: ['text', 'json', 'html'],
    },
  },
})

自定义覆盖率的报告器

我们可以通过在 test.coverage.reporter 中传递软件包名称或绝对路径来使用自定义覆盖报告器:

vitest.config.ts
ts
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 了解更多详情。

custom-reporter.cjs
js
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 来配置你的自定义覆盖率提供者:

vitest.config.ts
ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    coverage: {
      provider: 'custom',
      customProviderModule: 'my-custom-coverage-provider',
    },
  },
})

自定义覆盖率提供者需要一个 customProviderModule 选项,它是一个模块名称或从中加载 CoverageProviderModule 的路径。 它必须将实现 CoverageProviderModule 的对象导出为默认导出:

my-custom-coverage-provider.ts
ts
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 属性。

js
import { defineConfig } from 'vite'

export default defineConfig({
  test: {
    coverage: {
      reportsDirectory: './tests/unit/coverage',
    },
  },
})

代码忽略

两个覆盖率提供商都有自己的方法来忽略覆盖率报告中的代码:

使用 TypeScript 时,源码使用 esbuild 进行转译,这会从源码中删除所有注释(esbuild#516)。 被视为合法注释的注释将被保留。

你可以在忽略提示里加入 @preserve 关键字。 但要小心,这些忽略提示有可能会被打包进最终的生产环境构建中。

diff
-/* 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 选项,将覆盖率报告放在子目录中。
html coverage activation in Vitest UIhtml coverage activation in Vitest UIhtml coverage in Vitest UIhtml coverage in Vitest UI

Released under the MIT License.