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” 插件:

  1. 安装插件
  2. 运行覆盖率测试
  3. 查看编辑器中的覆盖率标记

忽略代码 #

忽略整行 #

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
[![Coverage Status](https://coveralls.io/repos/github/user/repo/badge.svg)](https://coveralls.io/github/user/repo)

[![codecov](https://codecov.io/gh/user/repo/branch/main/graph/badge.svg)](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