一、什么是代码覆盖率?
代码覆盖率测试 核心:
- 哪些步骤被完全执行了?
- 哪些步骤因为条件没达到被跳过了?
- 哪些步骤甚至从来没被看过?
这个检查被使用了多少的过程,就是覆盖率分析。它能帮你发现多余的、没用的、或者永远走不到的死角步骤。
二、Vue 2 覆盖率测试的运行机制(流程图)
整个流程的核心是 "代码插桩" 。就像在每个步骤后面加上一个打卡器,小机器人每做完一步,就在相应的打卡器上"滴"一下,记录自己来过。
以下是完整的流程图和步骤解析:

-
源代码插桩
- 工具 :使用 Istanbul (一个JS覆盖率工具)的插件(如
babel-plugin-istanbul
)。 - 过程 :在代码被打包(Webpack)或编译(Babel)之前,Istanbul 会遍历所有代码,并在每一个语句、分支、函数等位置注入一些"打卡"代码。转换后的代码不再是原始代码,而是被"插桩"后的代码。
- 工具 :使用 Istanbul (一个JS覆盖率工具)的插件(如
-
执行测试
- 工具 :Karma 测试运行器。它的作用是启动一个真实的浏览器(如 Chrome、Firefox),将插桩后的代码和你的测试用例加载到浏览器中执行。
- 为什么用浏览器? 因为 Vue 是一个前端框架,需要在浏览器环境中运行,用 Node.js 模拟的(如 JSDOM)环境可能不够真实。
- 你的测试代码(用 Mocha、Jasmine 等编写)会像用户一样操作和检查 Vue 组件。
-
收集覆盖率数据
- 当测试用例在浏览器中执行被插桩的代码时,每一步"打卡"都会被记录。
- 所有打卡结果会汇聚成一个包含覆盖率数据的全局对象,通常是
window.__coverage__
。
-
生成报告
- 工具 :Istanbul 的命令行工具
nyc
。 - 测试结束后,Karma 会将
window.__coverage__
对象从浏览器中取出,并写入一个coverage.json
文件。 nyc
命令会读取这个 JSON 文件,并生成各种格式(如 HTML、LCov)的、人类可读的覆盖率报告。
- 工具 :Istanbul 的命令行工具
-
分析报告
- 打开生成的
coverage/index.html
文件,你能看到一个非常详细的网页,清晰地展示了每个文件的覆盖率情况,甚至可以点击文件查看哪一行代码没有被执行到。
- 打开生成的
三、简单示例:看一个插桩前后的代码对比
假设我们有一段非常简单的 Vue 2 源码:
原始源码 (src/utils.js
)
javascript
export function isEven(num) {
if (num % 2 === 0) {
return true;
} else {
return false;
}
}
被 Istanbul 插桩后的代码
Istanbul 会把它变成这样(已简化,便于理解):
scss
import { increment } from "istanbul-lib-coverage";
// Istanbul 会创建一个全局的 coverage 对象来存储数据
const coverage = window.__coverage__ || (window.__coverage__ = {});
// 为这个文件创建一个计数器
coverage['src/utils.js'] = {
statementMap: { /* 记录每个语句的位置信息 */ },
fnMap: { /* 记录每个函数的位置信息 */ },
branchMap: { /* 记录每个分支(if/else)的位置信息 */ },
// 下面是核心:记录执行次数的计数器数组
s: [0, 0, 0, 0], // 每个语句的计数器,初始为0
f: [0], // 每个函数的计数器
b: [[0, 0]] // 每个分支的计数器,if 和 else 各一个
};
export function isEven(num) {
coverage['src/utils.js'].f[0]++; // 打卡:函数被调用了!
coverage['src/utils.js'].s[0]++; // 打卡:第一行语句
if (num % 2 === 0) {
coverage['src/utils.js'].b[0][0]++; // 打卡:走了 if 分支
coverage['src/utils.js'].s[1]++; // 打卡:return true 语句
return true;
} else {
coverage['src/utils.js'].b[0][1]++; // 打卡:走了 else 分支
coverage['src/utils.js'].s[2]++; // 打卡:return false 语句
return false;
}
}
coverage['src/utils.js'].s[3]++; // 打卡:函数定义后的结束语句
编写测试用例 (test/utils.spec.js
)
javascript
import { isEven } from '@/src/utils.js';
describe('isEven', () => {
it('should return true for even numbers', () => {
const result = isEven(2);
expect(result).to.be.true; // 这个测试只会触发 if 分支
});
// 如果我们注释掉下面这个测试用例
// it('should return false for odd numbers', () => {
// const result = isEven(1);
// expect(result).to.be.false; // 这个测试会触发 else 分支
// });
});
运行测试并生成报告
-
用 Karma 运行测试后,Istanbul 会分析
window.__coverage__
里的数据。 -
对于
src/utils.js
这个文件:- 函数覆盖率 :100% (
isEven
函数被调用了) - 语句覆盖率:100% (所有语句都执行了)
- 分支覆盖率 :50% (因为只走了
if
分支,else
分支没走!)
- 函数覆盖率 :100% (
查看报告
你会在 HTML 报告里看到 else { return false; }
这一行被标为黄色 或粉色 ,表示这行代码被执行了,但所在的分支没有完全覆盖。如果你完全没测这个函数,那所有代码都会是红色。
四、Vue 2 源码测试的特殊之处
对于 Vue 2 本身这个库,它的测试会更加复杂,但原理完全一样:
- 测试目标 :不再是业务组件,而是 Vue 的核心构造函数 、响应式系统 、虚拟DOM Diff 算法 、编译器等。
- 测试用例 :它们会直接调用
new Vue({...})
,或者调用Vue.compile(...)
等方法,然后断言其行为是否正确。 - 高覆盖率:像 Vue 这样的核心库,对覆盖率要求极高(通常保持在 99% 以上),确保每一个微小的功能变更都不会引起未知的崩溃。
总结
步骤 | 工具 | 目的 | 比喻 |
---|---|---|---|
1. 插桩 | babel-plugin-istanbul |
在源代码中注入"打卡"代码 | 给食谱的每一步装上打卡器 |
2. 运行 | Karma |
在真实浏览器环境中运行测试 | 让烘焙小机器人按食谱操作 |
3. 收集 | karma-coverage |
收集浏览器中的 __coverage__ 数据 |
收集所有打卡器的记录 |
4. 报告 | nyc |
生成可读的覆盖率报告(HTML) | 生成一份报告,显示哪些步骤没做 |
所以,Vue 2 源码的覆盖率测试就是一个 "插桩 → 运行 → 收集 → 报告" 的自动化过程,帮助你用科学的数据衡量测试的完备性,而不是盲目猜测。