在Cypress中测量代码覆盖率
你将学到
- 代码覆盖率与Cypress UI覆盖率的区别
- 如何为应用程序代码添加检测以测量代码覆盖率
- 如何从Cypress测试中收集代码覆盖率数据
- 如何合并并行测试的代码覆盖率
- 如何收集不同类型测试的代码覆盖率
介绍
随着编写的端到端测试越来越多,你可能会思考: 我需要编写更多测试吗?应用程序中是否还有未测试的部分?是否有部分代码被过度测试了?
Cypress提供了几种解决方案来回答这些问题:
代码覆盖率
代码覆盖率是一个指标,帮助你了解测试覆盖了多少应用程序代码。如果应用程序中有重要逻辑部分未被测试执行,那么可以添加新测试来确保这些部分的逻辑也被测试到。
通过代码覆盖率计算测试期间执行的源代码行数。代码覆盖率需要在运行代码前通过插桩在源代码中插入额外的计数器。本文档将重点介绍代码覆盖率及设置所需的插桩。
UI覆盖率
UI覆盖率是基于应用程序交互元素的视觉测试覆盖率报告。它直接在应用程序的每个页面中突出显示未测试到的区域,帮助你做出数据驱动的测试决策。
UI覆盖率还能与Cypress测试无缝集成,基于已编写的测试构建——无需像代码覆盖率那样进行额外插桩。
要了解更多关于UI覆盖率的信息,请参阅我们的UI覆盖率指南。

代码插桩
插桩将如下代码...
function add(a, b) {
return a + b
}
module.exports = { add }
...解析并找到所有函数、语句和分支,然后在代码中插入计数器。对于上述代码,插桩后可能如下:
// 此对象统计每个函数和语句的执行次数
const c = (window.__coverage__ = {
// "f"统计每个函数的调用次数
// 源代码中只有一个函数,因此以[0]开始
f: [0],
// "s"统计每个语句的调用次数
// 有3个语句,都以0开始
s: [0, 0, 0],
})
// 原始代码 + 增量语句
// 使用"c"作为"window.__coverage__"对象的别名
// 第一个语句定义函数,增加计数器
c.s[0]++
function add(a, b) {
// 函数被调用,然后执行第二个语句
c.f[0]++
c.s[1]++
return a + b
}
// 即将执行第三个语句
c.s[2]++
module.exports = { add }
假设我们从测试文件中加载上述插桩后的源文件。一些计数器会立即增加!
const { add } = require('./add')
// JavaScript引擎已解析并评估"add.js"源代码
// 这运行了一些增量语句
// __coverage__现在有
// f: [0] - 函数"add"未被调用
// s: [1, 0, 1] - 第一个和第三个计数器增加了
// 但函数"add"内的语句未被调用
我们希望确保文件add.js
中的每个语句和函数至少被测试执行一次。因此我们编写一个测试:
const { add } = require('./add')
it('adds numbers', () => {
expect(add(2, 3)).to.equal(5)
})
当测试调用add(2, 3)
时,"add"函数内的计数器增加,覆盖率对象变为:
{
// "f"记录每个函数的调用次数
// 源代码中只有一个函数
// 因此以[0]开始
f: [1],
// "s"记录每个语句的调用次数
// 有3个语句,都以0开始
s: [1, 1, 1]
}
这个单一测试实现了100%的代码覆盖率——每个函数和语句至少被执行了一次。但在实际应用中,实现100%代码覆盖率需要多个测试。
测试完成后,覆盖率对象可以被序列化并保存到磁盘,以便生成人类友好的报告。收集的覆盖率信息也可以发送到外部服务,帮助进行拉取请求审查。
Cypress不会对你的代码进行插桩——你需要自己完成。JavaScript代码插桩的黄金标准是久经考验的Istanbul,幸运的是,它与Cypress配合得很好。你可以通过以下两种方式之一在构建步骤中对代码进行插桩:
- 使用nyc模块——Istanbul库的命令行接口
- 作为代码转译管道的一部分,使用
babel-plugin-istanbul
工具
使用NYC
要对src
文件夹中的应用程序代码进行插桩并将结果保存在instrumented
文件夹中,使用以下命令:
npx nyc instrument --compact=false src instrumented
我们传递--compact=false
标志以生成人类友好的输出。
插桩将你的原始代码片段...
const store = createStore(reducer)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
...包装每个语句,添加计数器以跟踪JavaScript运行时执行了多少次每个源代码行。
const store = (cov_18hmhptych.s[0]++, createStore(reducer))
cov_18hmhptych.s[1]++
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
注意cov_18hmhptych.s[0]++
和cov_18hmhptych.s[1]++
的调用,它们增加了语句计数器。所有计数器和额外的簿记信息都存储在附加到浏览器window
对象的单个对象中。如果我们提供instrumented
文件夹而不是src
并打开应用程序,可以看到这些计数器。

如果我们深入覆盖率对象,可以看到每个文件中执行的语句。例如文件src/index.js
有以下信息:

绿色高亮显示了该文件中的4个语句。前三个语句各执行了一次,最后一个语句从未执行(可能在一个if
语句内)。通过使用应用程序,我们可以增加计数器并将一些零计数器变为正数。
使用代码转译管道
除了使用npx instrument
命令,我们可以使用babel-plugin-istanbul
在转译过程中对代码进行插桩。将此插件添加到.babelrc
文件中。
{
"presets": ["@babel/preset-react"],
"plugins": ["transform-class-properties", "istanbul"]
}
现在我们可以提供应用程序并获得插桩后的代码,无需中间文件夹,但结果是相同的插桩代码被浏览器加载,相同的window.__coverage__
对象跟踪原始语句。
查看@cypress/code-coverage#examples
获取展示不同代码覆盖率设置的全示例项目。

nyc和babel-plugin-istanbul
的一个非常好的特性是自动生成源映射,允许我们收集代码覆盖率信息,同时在开发者工具中与原始非插桩代码交互。在上面的截图中,打包文件(绿色箭头)有覆盖率计数器,但绿色矩形中的源映射文件显示原始代码。
nyc
和babel-plugin-istanbul
只对应用程序代码进行插桩,不对node_modules
中的第三方依赖进行插桩。