Jest 代码覆盖率 #
覆盖率概述 #
代码覆盖率是衡量测试质量的重要指标,表示代码被测试执行覆盖的程度。
text
┌─────────────────────────────────────────────────────────────┐
│ 覆盖率类型 │
├─────────────────────────────────────────────────────────────┤
│ 行覆盖率 (Line) - 每行代码是否被执行 │
│ 分支覆盖率 (Branch) - 每个分支是否被执行 │
│ 函数覆盖率 (Function) - 每个函数是否被调用 │
│ 语句覆盖率 (Statement)- 每条语句是否被执行 │
└─────────────────────────────────────────────────────────────┘
基本使用 #
运行覆盖率 #
bash
# 命令行
jest --coverage
# npm script
npm test -- --coverage
# 简写
jest --collectCoverage
输出报告 #
text
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 90.5 | 85.2 | 92.1 | 90.5 |
sum.js | 100 | 100 | 100 | 100 |
math.js | 85.7 | 75 | 88.9 | 85.7 | 12-15
----------|---------|----------|---------|---------|-------------------
配置覆盖率 #
jest.config.js #
javascript
module.exports = {
// 收集覆盖率
collectCoverage: true,
// 覆盖率报告目录
coverageDirectory: 'coverage',
// 收集覆盖率的文件
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/index.js',
'!**/node_modules/**',
],
// 覆盖率报告格式
coverageReporters: ['text', 'lcov', 'html'],
// 覆盖率阈值
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};
package.json #
json
{
"jest": {
"collectCoverage": true,
"coverageDirectory": "coverage",
"collectCoverageFrom": [
"src/**/*.{js,jsx,ts,tsx}",
"!src/**/*.d.ts"
],
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
覆盖率报告格式 #
文本报告 #
javascript
coverageReporters: ['text', 'text-summary'],
输出到控制台。
HTML 报告 #
javascript
coverageReporters: ['html'],
生成可浏览的 HTML 报告:
text
coverage/
├── lcov-report/
│ ├── index.html
│ ├── src/
│ │ ├── index.html
│ │ ├── sum.js.html
│ │ └── math.js.html
│ └── ...
LCOV 报告 #
javascript
coverageReporters: ['lcov'],
生成 lcov.info 文件,用于 CI 工具集成。
JSON 报告 #
javascript
coverageReporters: ['json'],
生成 coverage-final.json 文件。
多种格式组合 #
javascript
coverageReporters: [
'text', // 控制台详细报告
'text-summary', // 控制台摘要
'html', // HTML 报告
'lcov', // LCOV 格式
'json', // JSON 格式
'clover', // Clover XML
'cobertura', // Cobertura XML
],
覆盖率阈值 #
全局阈值 #
javascript
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
文件级阈值 #
javascript
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
'./src/utils/': {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
'./src/components/': {
branches: 70,
functions: 70,
lines: 70,
statements: 70,
},
},
负数阈值 #
javascript
coverageThreshold: {
global: {
branches: -10, // 允许最多 10 个未覆盖分支
functions: -5, // 允许最多 5 个未覆盖函数
lines: -20, // 允许最多 20 行未覆盖
},
},
收集覆盖率文件 #
包含文件 #
javascript
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}', // 所有源文件
'lib/**/*.js', // lib 目录
],
排除文件 #
javascript
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts', // 排除类型声明
'!src/index.js', // 排除入口文件
'!src/**/*.test.{js,ts}', // 排除测试文件
'!**/node_modules/**', // 排除 node_modules
'!**/__tests__/**', // 排除测试目录
],
覆盖率忽略 #
忽略特定代码 #
javascript
// istanbul ignore next
function debugLog(message) {
console.log(message);
}
// istanbul ignore else
if (process.env.NODE_ENV === 'production') {
// 生产环境代码
} else {
// 开发环境代码
}
/* istanbul ignore file */
// 整个文件忽略
export function helper() {}
忽略函数 #
javascript
function calculate(a, b) {
/* istanbul ignore next */
if (process.env.DEBUG) {
console.log('Calculating...');
}
return a + b;
}
CI 集成 #
GitHub Actions #
yaml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm test -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
GitLab CI #
yaml
test:
script:
- npm ci
- npm test -- --coverage
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
Travis CI #
yaml
language: node_js
node_js:
- 18
script:
- npm test -- --coverage
after_success:
- bash <(curl -s https://codecov.io/bash)
覆盖率服务 #
Codecov #
bash
# 安装
npm install --save-dev codecov
# 上传
npm run test -- --coverage
npx codecov
Coveralls #
bash
# 安装
npm install --save-dev coveralls
# 上传
npm run test -- --coverage
npx coveralls < coverage/lcov.info
SonarQube #
properties
# sonar-project.properties
sonar.projectKey=my-project
sonar.sources=src
sonar.tests=src
sonar.test.inclusions=**/*.test.js
sonar.javascript.lcov.reportPaths=coverage/lcov.info
覆盖率徽章 #
README 徽章 #
markdown
[](https://codecov.io/gh/user/repo)
[](https://coveralls.io/github/user/repo)
实际示例 #
项目配置 #
javascript
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
collectCoverage: false, // 默认不收集
coverageDirectory: 'coverage',
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/index.tsx',
'!src/reportWebVitals.ts',
'!**/node_modules/**',
],
coverageReporters: [
'text',
'text-summary',
'html',
'lcov',
],
coverageThreshold: {
global: {
branches: 70,
functions: 70,
lines: 70,
statements: 70,
},
},
};
package.json scripts #
json
{
"scripts": {
"test": "jest",
"test:coverage": "jest --coverage",
"test:coverage:open": "npm run test:coverage && open coverage/lcov-report/index.html"
}
}
查看覆盖率报告 #
bash
# 运行覆盖率
npm run test:coverage
# 打开 HTML 报告
open coverage/lcov-report/index.html
覆盖率解读 #
覆盖率指标含义 #
| 指标 | 描述 | 计算方式 |
|---|---|---|
| Statements | 语句覆盖率 | 执行语句数 / 总语句数 |
| Branches | 分支覆盖率 | 执行分支数 / 总分支数 |
| Functions | 函数覆盖率 | 调用函数数 / 总函数数 |
| Lines | 行覆盖率 | 执行行数 / 总行数 |
覆盖率不是越高越好 #
text
┌─────────────────────────────────────────────────────────────┐
│ 覆盖率误区 │
├─────────────────────────────────────────────────────────────┤
│ ❌ 100% 覆盖率 = 没有 Bug │
│ ❌ 覆盖率越高越好 │
│ ❌ 追求 100% 覆盖率 │
│ │
│ ✅ 覆盖率是质量指标之一 │
│ ✅ 关注关键代码的覆盖 │
│ ✅ 平衡开发效率和覆盖率 │
└─────────────────────────────────────────────────────────────┘
合理的覆盖率目标 #
| 项目类型 | 建议覆盖率 |
|---|---|
| 核心库 | 90%+ |
| 业务应用 | 70-80% |
| UI 组件 | 60-70% |
| 工具函数 | 90%+ |
提高覆盖率技巧 #
1. 测试边界情况 #
javascript
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
// 测试
test('divide', () => {
expect(divide(10, 2)).toBe(5);
expect(() => divide(10, 0)).toThrow('Division by zero');
});
2. 测试错误路径 #
javascript
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error('User not found');
}
return response.json();
}
// 测试
test('fetchUser error', async () => {
jest.spyOn(global, 'fetch').mockResolvedValue({ ok: false });
await expect(fetchUser(1)).rejects.toThrow('User not found');
});
3. 测试条件分支 #
javascript
function getDiscount(user) {
if (user.isPremium) {
return 0.2;
} else if (user.isMember) {
return 0.1;
}
return 0;
}
// 测试所有分支
test('getDiscount', () => {
expect(getDiscount({ isPremium: true })).toBe(0.2);
expect(getDiscount({ isMember: true })).toBe(0.1);
expect(getDiscount({})).toBe(0);
});
4. 使用参数化测试 #
javascript
test.each([
[1, 2, 3],
[2, 3, 5],
[-1, 1, 0],
])('add(%i, %i) = %i', (a, b, expected) => {
expect(add(a, b)).toBe(expected);
});
常见问题 #
覆盖率不准确 #
javascript
// 问题:覆盖率显示不正确
// 解决方案:清除缓存
jest --clearCache
覆盖率报告不完整 #
javascript
// 问题:某些文件没有出现在报告中
// 解决方案:检查 collectCoverageFrom 配置
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
// 确保包含所有需要测试的文件
],
阈值检查失败 #
bash
# 问题:覆盖率低于阈值
# 解决方案:
# 1. 增加测试用例
# 2. 调整阈值
# 3. 忽略不需要测试的代码
最佳实践 #
1. 设置合理的阈值 #
javascript
coverageThreshold: {
global: {
branches: 70, // 不要设置太高
functions: 70,
lines: 70,
statements: 70,
},
},
2. 定期检查覆盖率 #
bash
# CI 中强制覆盖率检查
npm test -- --coverage --ci
3. 关注关键代码 #
javascript
// 关键代码设置更高阈值
coverageThreshold: {
'./src/core/': {
branches: 90,
functions: 90,
lines: 90,
},
},
4. 使用覆盖率报告 #
bash
# 定期查看 HTML 报告
npm run test:coverage
open coverage/lcov-report/index.html
下一步 #
现在你已经掌握了 Jest 代码覆盖率,接下来学习 高级配置 深入了解 Jest 配置!
最后更新:2026-03-28