JavaScript测试的代码覆盖率
代码覆盖率(Code Coverage)在测试中一个非常重要的指标,它可以帮助来衡量测试的质量,一般来说,代码的有效覆盖率越高越好;它也可以反过来衡量代码本身,如果一段代码永远都无法用合理的测试案例覆盖,那么它有可能是一段冗余的代码。
JavaScript代码覆盖率的指标
- Bytes 字节覆盖率,仅原生V8覆盖率支持
- Statements 语句覆盖率
- Branches 分支覆盖率,比如if,switch这些
- Functions 函数覆盖率
- Lines 行覆盖率
两种JavaScript代码覆盖率的数据格式
- V8 coverage - JS V8引擎原生支持的覆盖率数据格式
- 优点 - 原生支持,无需预编译,性能高; 支持CSS的覆盖率;支持压缩的代码;
- 缺点 - 由于是使用V8引擎,所以支持Nodejs以及基于Chromium的浏览器;对源码需sourcemap支持(sourcemap本身由不同的编译工具生成,存在一些不确定性);然后就是V8覆盖率数据本身仅包含Bytes字节覆盖指标,和部分functions覆盖指标,其他指标需分析AST进行计算;
- Istanbul - 老牌js覆盖率支持工具所支持的格式
- 优点 - 支持所有JS运行环境,收集覆盖率数据简单,运行时将自动累加统计;除了Bytes指标,其他都支持;
- 缺点 - 需要预编译源代码,将代码统计的计数器代码逐行插入源代码中,所有代码量大的时候,速度较慢;同样有对源码的sourcemap支持的问题,尤其是一些vue,xml等一些非原生js语法的代码;然后就是不同的编译工具,它需要专门的解析器,比如babel-plugin-istanbul vite-plugin-istanbul等等,可能导致格式混乱等问题;
主流测试工具的代码覆盖率支持情况
测试框架 | 类型 | 自带代码覆盖率报告支持 | 数据源 |
---|---|---|---|
Playwright | e2e | ❌ | V8 |
Puppeteer | e2e | ❌ | V8 |
Cypress | e2e | ✅ | Istanbul |
CodeceptJS | e2e | ✅ | V8 |
WebdriverIO | e2e | ✅ | Istanbul |
Storybook Test Runner | e2e | ✅ | Istanbul |
TestCafe | e2e | ❌ | |
Selenium Webdriver | e2e | ❌ | |
Jest | unit | ✅ | Istanbul/V8 |
Vitest | unit | ✅ | Istanbul/V8 |
Mocha | unit/e2e | ❌ |
2024年如何选择测试工具?
如何解决Playwright的代码覆盖率报告
Playwright自动化测试工具@playwright/test
并没有官方提供生成代码覆盖率报告的功能,但提供了收集V8覆盖率数据的接口,也就是可以通过使用第三方工具解决,这里推荐使用工具monocart-coverage-reports,下面简称MCR
- 1,首先,使用MCR创建一个代码覆盖率报告实例
const MCR = require('monocart-coverage-reports');
const mcr = MCR({
name: 'My Coverage Report - 2024-04-30',
outputDir: './coverage-reports',
reports: ["v8", "console-details"],
cleanCache: true
});
- 2,然后,需要在每个测试阶段开始之前,开启浏览器的V8 Coverage覆盖率数据收集
await Promise.all([
page.coverage.startJSCoverage({
// reportAnonymousScripts: true,
resetOnNavigation: false
}),
page.coverage.startCSSCoverage({
// Note, anonymous styles (without sourceURLs) are not supported, alternatively, you can use CDPClient
resetOnNavigation: false
})
]);
- 3,接着,在测试阶段完成的时候,收集V8覆盖率数据,并将数据添加到覆盖率报告MCR中
const [jsCoverage, cssCoverage] = await Promise.all([
page.coverage.stopJSCoverage(),
page.coverage.stopCSSCoverage()
]);
const coverageData = [... jsCoverage, ... cssCoverage];
await mcr.add(coverageData);
- 4,最后,在所有测试完成的时候,调用MCR的generate接口生成代码覆盖率报告
await mcr.generate();
测试报告将保存到步骤1中指定设置的位置,2和3的步骤可能调用多次
以上是基本的代码覆盖率报告生成逻辑,如何与Playwright集成呢?
集成MCR到Playwright
- 因为playwright默认就是多任务并行的,所以我们可以使用playwright的
autoTestFixture
来自动收集和添加覆盖率数据,也就是上面的步骤2和3 - 然后创建全局的回调 global.setup 和 global-teardown 来完成步骤1和4
完整的例子可见仓库:playwright-coverage
Playwright如何支持前后端代码的覆盖率报告?
就以时下流行的Nextjs框架为例,它是一个基于React的前后端代码一起写的框架,同一个文件的代码有可能有的在前端运行,有的在后端运行。那么当测试完成的时候,我们如何能得到整个代码的覆盖率情况?
- 首先需要开启nextjs的sourcemap在dev的支持next.config.js
// next.config.js
const nextConfig = {
webpack: (config) => {
if (process.env.NODE_V8_COVERAGE) {
Object.defineProperty(config, 'devtool', {
get() {
return 'source-map';
},
set() {}
});
}
return config;
}
};
export default nextConfig;
- 然后使用下面两个环境变量来运行
next dev
// package.json
"scripts": {
"test:start": "cross-env NODE_V8_COVERAGE=.v8-coverage NODE_OPTIONS=--inspect=9229 next dev",
}
NODE_V8_COVERAGE
是指定收集的后端v8覆盖率数据存储位置
NODE_OPTIONS
是开启Nodejs调试,用于何时停止收集后端覆盖率数据
- 前端代码的覆盖率收集就使用上面常规的方法,1,2,3步骤
- 对于后端代码,由于已经使用NODE_V8_COVERAGE开启了收集,但还需要在global-teardown的时候来手动停止收集并写入硬盘目录,否则playwright全部测试完成后,会是强制关闭后端进程,导致后端覆盖率数据来不及写入硬盘
完整的例子可见仓库:nextjs-with-playwright
如何合并单元测试和端对端自动化测试的代码覆盖率报告?
在实际的项目开发中,我们多数情况是单元测试和自动化测试并存的,因为单元测试很多时候无法覆盖到一些整合的情况,还有一些需要交互情况,比如用户点击鼠标按键盘,拖动等等,这里就需要e2e自动化测试补充。如果使用Jest完成了单元测试,然后又用Playwright完成了e2e的测试,那么我怎么能知道整个代码的覆盖率情况呢? 下面以MCR工具为例来实现如何合并覆盖率报告:
- 首先需要分别导出raw格式的覆盖率报告
- 比如Jest我们可以使用jest-monocart-coverage插件来导出raw格式覆盖率报告
- Playwright使用上面集成MCR的方法也同样导出raw格式覆盖率报告
- 最后使用MCR支持的
inputDir
参数导入2个raw的报告来合并unit和e2e的报告
关于合并代码覆盖率报告的代码例子见:merge-coverage-reports
总结
JavaScript的代码覆盖率非常重要,但目前对于绝大多数国内公司,不论项目大小,一般都没有花功夫去支持。主要原因是不够重视测试和代码质量,还有就是成本高成效低。相对来说,国外的公司会更加的注重测试和质量一些。
其实以我个人的经验来说,使用了对的工具,其实还是比较容易建立起一套测试和质量保障体系的。无论是单元测试还是自动化测试,长久看它更多的保障了代码的质量,反而是更加节约成本的,很多改动带来的问题可以扼杀在摇篮里。希望上面的关于代码覆盖率的介绍能帮到你,也欢迎留言交流。
转载自:https://juejin.cn/post/7363452651797217306