Vitest 代码覆盖率 #
覆盖率概述 #
代码覆盖率是衡量测试质量的重要指标,它表示测试代码执行了多少源代码。高覆盖率意味着更多的代码被测试覆盖,有助于发现潜在的 bug。
覆盖率类型 #
text
┌─────────────────────────────────────────────────────────────┐
│ 覆盖率类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 行覆盖率(Line Coverage) │
│ 测试执行了多少行代码 │
│ │
│ 2. 函数覆盖率(Function Coverage) │
│ 测试调用了多少函数 │
│ │
│ 3. 分支覆盖率(Branch Coverage) │
│ 测试覆盖了多少条件分支 │
│ │
│ 4. 语句覆盖率(Statement Coverage) │
│ 测试执行了多少语句 │
│ │
└─────────────────────────────────────────────────────────────┘
安装覆盖率提供者 #
v8 提供者(推荐) #
bash
npm install -D @vitest/coverage-v8
istanbul 提供者 #
bash
npm install -D @vitest/coverage-istanbul
两者对比 #
| 特性 | v8 | istanbul |
|---|---|---|
| 性能 | 更快 | 较慢 |
| 准确性 | 原生支持 | 更精确 |
| 兼容性 | Node.js 10+ | 更广泛 |
| 推荐 | ✅ 推荐 | 特殊场景 |
基本用法 #
运行覆盖率测试 #
bash
# 基本命令
vitest --coverage
# 或在 package.json 中添加脚本
{
"scripts": {
"test:coverage": "vitest --coverage"
}
}
输出示例 #
text
% Coverage report from v8
---------------------------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
---------------------------|---------|----------|---------|---------|
All files | 85.5 | 72.3 | 90.1 | 85.5 |
src/utils.ts | 95.2 | 88.9 | 100.0 | 95.2 |
src/api.ts | 78.3 | 65.4 | 83.3 | 78.3 |
src/components | 82.1 | 70.0 | 87.5 | 82.1 |
Button.tsx | 90.0 | 80.0 | 100.0 | 90.0 |
Input.tsx | 75.0 | 60.0 | 75.0 | 75.0 |
---------------------------|---------|----------|---------|---------|
配置覆盖率 #
基本配置 #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
provider: 'v8', // 或 'istanbul'
reporter: ['text', 'json', 'html'],
},
},
})
完整配置 #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
// 提供者
provider: 'v8',
// 报告器
reporter: ['text', 'json', 'html', 'lcov'],
// 报告目录
reportsDirectory: './coverage',
// 包含的文件
include: [
'src/**/*.ts',
'src/**/*.tsx',
],
// 排除的文件
exclude: [
'node_modules/**',
'test/**',
'**/*.d.ts',
'**/*.config.*',
'**/types/**',
],
// 扩展名
extension: ['.ts', '.tsx'],
// 是否包含所有文件(即使没有测试)
all: true,
// 覆盖率阈值
lines: 80,
functions: 80,
branches: 80,
statements: 80,
// 每个文件的阈值
perFile: true,
// 阈值自动更新
autoUpdate: false,
// 是否允许外部文件
allowExternal: false,
// 是否跳过全覆盖的文件
skipFull: false,
// 清理覆盖率报告
clean: true,
// 清理时是否清理所有报告
cleanOnRerun: true,
},
},
})
报告器类型 #
文本报告(text) #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
reporter: ['text'],
},
},
})
输出到控制台:
text
---------------------------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
---------------------------|---------|----------|---------|---------|
All files | 85.5 | 72.3 | 90.1 | 85.5 |
---------------------------|---------|----------|---------|---------|
JSON 报告(json) #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
reporter: ['json'],
},
},
})
生成 coverage-final.json 文件。
HTML 报告(html) #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
reporter: ['html'],
},
},
})
生成可交互的 HTML 报告:
text
coverage/
├── index.html
├── base.css
├── prettify.css
├── prettify.js
└── src/
├── utils.ts.html
└── api.ts.html
LCOV 报告(lcov) #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
reporter: ['lcov'],
},
},
})
生成 lcov.info 文件,可用于 CI 集成。
多种报告器 #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
reporter: [
'text', // 控制台输出
'json', // JSON 文件
'html', // HTML 报告
'lcov', // LCOV 格式
'clover', // Clover XML
'cobertura', // Cobertura XML
],
},
},
})
覆盖率阈值 #
全局阈值 #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
lines: 80, // 行覆盖率 >= 80%
functions: 80, // 函数覆盖率 >= 80%
branches: 70, // 分支覆盖率 >= 70%
statements: 80, // 语句覆盖率 >= 80%
},
},
})
文件级阈值 #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
perFile: true, // 检查每个文件的覆盖率
lines: 80,
functions: 80,
branches: 70,
statements: 80,
},
},
})
阈值失败 #
当覆盖率低于阈值时,测试失败:
bash
ERROR: Coverage for lines (75.5%) does not meet global threshold (80%)
ERROR: Coverage for branches (65.3%) does not meet global threshold (70%)
包含和排除 #
包含特定文件 #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
include: [
'src/**/*.ts',
'src/**/*.tsx',
'!src/**/*.test.ts',
'!src/**/*.spec.ts',
],
},
},
})
排除特定文件 #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
exclude: [
'node_modules/**',
'test/**',
'**/*.d.ts',
'**/*.config.*',
'src/types/**',
'src/**/index.ts',
],
},
},
})
查看覆盖率报告 #
HTML 报告 #
bash
# 生成 HTML 报告
vitest --coverage
# 打开报告
open coverage/index.html
VS Code 插件 #
安装 “Coverage Gutters” 插件:
- 安装插件
- 运行覆盖率测试
- 查看编辑器中的覆盖率标记
忽略代码 #
忽略整行 #
typescript
// istanbul ignore next
const result = complexCalculation() // istanbul ignore next
忽略函数 #
typescript
/* istanbul ignore next */
function debugLog(message: string) {
console.log(message)
}
忽略分支 #
typescript
if (condition) {
doSomething()
} /* istanbul ignore else */ else {
doSomethingElse()
}
忽略特定代码 #
typescript
/* istanbul ignore if */
if (process.env.NODE_ENV === 'test') {
// 测试环境特殊处理
}
CI 集成 #
GitHub Actions #
yaml
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run test:coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
Codecov 集成 #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
reporter: ['lcov'], // Codecov 需要 lcov 格式
},
},
})
Coveralls 集成 #
yaml
# .github/workflows/test.yml
- name: Coveralls
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
覆盖率徽章 #
添加到 README #
markdown
[](https://coveralls.io/github/user/repo)
[](https://codecov.io/gh/user/repo)
高级配置 #
自定义报告目录 #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
reportsDirectory: './reports/coverage',
},
},
})
自定义报告器选项 #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
reporter: [
['html', { subdir: 'html' }],
['json', { file: 'coverage.json' }],
['lcov', { file: 'lcov.info' }],
],
},
},
})
条件覆盖率 #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
// CI 环境更严格的阈值
...(process.env.CI && {
lines: 90,
functions: 90,
branches: 85,
statements: 90,
}),
},
},
})
覆盖率最佳实践 #
1. 设置合理的阈值 #
typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
// 渐进式提高阈值
lines: 80, // 从 80% 开始
functions: 80,
branches: 70, // 分支覆盖率通常较低
statements: 80,
},
},
})
2. 关注未覆盖的代码 #
bash
# 查看详细报告
vitest --coverage
# 打开 HTML 报告查看未覆盖的行
open coverage/index.html
3. 不要追求 100% 覆盖率 #
typescript
// 有些代码不需要测试
/* istanbul ignore next */
if (process.env.NODE_ENV === 'development') {
// 开发环境调试代码
console.log('Debug info:', data)
}
4. 测试关键路径 #
typescript
// 优先测试核心业务逻辑
// 而不是所有代码路径
// utils.test.ts
describe('critical functions', () => {
test('calculatePrice handles all cases', () => {
// 测试所有分支
expect(calculatePrice(100, 'standard')).toBe(100)
expect(calculatePrice(100, 'premium')).toBe(90)
expect(calculatePrice(100, 'vip')).toBe(80)
})
})
5. 定期检查覆盖率 #
yaml
# .github/workflows/test.yml
- name: Check coverage
run: |
npm run test:coverage
# 如果覆盖率下降,CI 失败
常见问题 #
1. 覆盖率报告不准确 #
typescript
// 确保配置正确
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
provider: 'v8',
all: true, // 包含所有文件
},
},
})
2. 某些文件未显示 #
typescript
// 检查 include/exclude 配置
// vitest.config.ts
import { defineConfig } from 'vitest'
export default defineConfig({
test: {
coverage: {
include: ['src/**'], // 确保包含
exclude: ['src/**/*.d.ts'], // 排除类型定义
},
},
})
3. 分支覆盖率低 #
typescript
// 添加更多测试用例覆盖分支
test('handles all branches', () => {
// if 分支
expect(getMessage(true)).toBe('Yes')
// else 分支
expect(getMessage(false)).toBe('No')
})
覆盖率速查表 #
| 配置项 | 说明 |
|---|---|
provider |
覆盖率提供者(v8/istanbul) |
reporter |
报告器类型 |
reportsDirectory |
报告目录 |
include |
包含的文件 |
exclude |
排除的文件 |
all |
包含所有文件 |
lines |
行覆盖率阈值 |
functions |
函数覆盖率阈值 |
branches |
分支覆盖率阈值 |
statements |
语句覆盖率阈值 |
perFile |
文件级阈值检查 |
skipFull |
跳过全覆盖文件 |
下一步 #
现在你已经掌握了代码覆盖率,接下来学习 性能测试 了解如何进行基准测试!
最后更新:2026-03-28