当前位置:   article > 正文

[译] Vue 应用的代码覆盖率

vue代码覆盖率插件
  • 原文地址:https://vuejsdevelopers.com/2020/07/20/code-coverage-vue-cypress/

  • 原文作者:Gleb Bahmutov

  • 译文出自:"掘金翻译计划"(https://github.com/xitu/gold-miner)

  • 本文永久链接:https://github.com/xitu/gold-miner/blob/master/article/2020/code-coverage-vue-cypress.md


让我们像 ????bahmutov/vue-calculator 应用一样,借助 ????Vue CLI 来搭建一个 Vue 应用脚手架。在本文中,我将展示如何测量应用的源代码以收集其代码覆盖率信息。其后我们将利用该代码覆盖率报告来引导端到端测试的编写。

应用

示例应用可在 ????bahmutov/vue-calculator 找到,该仓库 fork 自脚手架阶段使用了 Vue CLI 默认模版的 ????kylbutlr/vue-calculator 项目。其代码使用如下 babel.config.js  文件转译:

  1. // babel.config.js
  2. module.exports = {
  3.   presets: [
  4.     '@vue/app'
  5.   ]
  6. }

当我们用 npm run serve 启动应用时,其实是执行了这条 NPM script

  1. {
  2.   "scripts": {
  3.     "serve""vue-cli-service serve"
  4.   }
  5. }

应用默认运行在 8080 端口上。

Vue 计算器应用

搞定!你可以计算任何想要的东西了。

测量源代码

我们可以通过向 Babel 配置文件导出对象中添加 plugins 列表来测量应用代码。该插件列表应包含 ????babel-plugin-istanbul 。

  1. // babel.config.js
  2. module.exports = {
  3.   presets: [
  4.     '@vue/app'
  5.   ],
  6.   plugins: [
  7.     'babel-plugin-istanbul'
  8.   ]
  9. }

现在,当应用运行时,我们应该能找到 window.__coverage__ 对象,该对象包含了每条语句、每个函数,及每个文件的每一个分支的各种计数。

应用覆盖率对象

不过上面展示的覆盖率对象,仅包含了单一条目 src/main.js,却缺失了 src/App.vuesrc/components/Calculator.vue 两个文件。

让我们来告诉 babel-plugin-istanbul 我们想要同时测量 .js.vue 文件吧。

  1. // babel.config.js
  2. module.exports = {
  3.   presets: [
  4.     '@vue/app'
  5.   ],
  6.   plugins: [
  7.     ['babel-plugin-istanbul', {
  8.       extension: ['.js''.vue']
  9.     }]
  10.   ]
  11. }

提示: 我们可以将 istanbul 设置放在一个单独的 .nycrc 文件中(译注:????nyc ,Istanbul 提供的命令行接口工具),或将它们添加到 package.json。目前而言,还是先将这些设置一起保留在插件列表本身中吧。

当我们重启应用后,得到了一个包含 .js.vue 文件条目的新 window.__coverage__ 对象。

被测量的 JS 和 Vue 文件

条件性测量

如果你观察应用的打包结果,就会看到测量所做的事情。其围绕每条语句都插入了计数器,用以保持跟踪一条语句被执行了多少次。对于每一个函数和每一个分支路径,也有单独的计数器。

被测量的源代码

我们并不想测量生产环境代码。应仅在 NODE_ENV=test 时测量代码,好利用收集到的代码覆盖率帮助我们编写更好的测试。

  1. // babel.config.js
  2. const plugins = []
  3. if (process.env.NODE_ENV === 'test') {
  4.   plugins.push([
  5.     "babel-plugin-istanbul", {
  6.       // 在此为 NYC 测量工具指定一些选项
  7.       // 如告知其同时测量 JavaScript 和 Vue 文件
  8.       extension: ['.js''.vue'],
  9.     }
  10.   ])
  11. }
  12. module.exports = {
  13.   presets: [
  14.     '@vue/app'
  15.   ],
  16.   plugins
  17. }

可以通过设置环境变量启动带测量的应用。

$ NODE_ENV=test npm run serve

提示: 对于跨平台可移植性,可使用 ????cross-env 工具设置一个环境变量。

端到端测试

现在我们测量了源代码,使用其引导编写测试吧。我将用官方的 Vue CLI 插件 ????@vue/cli-plugin-e2e-cypress 安装 Cypress Test Runner。而后我将安装 ????Cypress 代码覆盖率插件 以在测试运行结束时将覆盖率对象转换为人和机器皆可读的报告。

  1. $ vue add e2e-cypress
  2. $ npm i -D @cypress/code-coverage
  3. + @cypress/code-coverage@3.8.1

????@vue/cli-plugin-e2e-cypress 已经创建了 tests/e2e 文件夹,在其 support 和 plugins 子目录的文件中都可以加载代码覆盖率插件。

  1. // 文件 tests/e2e/support/index.js
  2. import '@cypress/code-coverage/support'
  3. // 文件 tests/e2e/plugins/index.js
  4. module.exports = (on, config) => {
  5.   require('@cypress/code-coverage/task')(on, config)
  6.   // 重要:须返回包含任何改变过的环境变量的配置对象
  7.   return config
  8. }

让我们为被 ????@vue/cli-plugin-e2e-cypress 插入到 package.json 中的 NPM script 命令 test:e2e  设置环境变量 NODE_ENV=test

  1. {
  2.   "scripts": {
  3.     "serve""vue-cli-service serve",
  4.     "build""vue-cli-service build",
  5.     "test:e2e""NODE_ENV=test vue-cli-service test:e2e"
  6.   }
  7. }

可将我们的首个端到端规格文件置于 tests/e2e/integration 文件夹:

  1. /// <reference types="cypress" />
  2. describe('Calculator', () => {
  3.   beforeEach(() => {
  4.     cy.visit('/')
  5.   })
  6.   it('computes', () => {
  7.     cy.contains('.button'2).click()
  8.     cy.contains('.button'3).click()
  9.     cy.contains('.operator''+').click()
  10.     cy.contains('.button'1).click()
  11.     cy.contains('.button'9).click()
  12.     cy.contains('.operator''=').click()
  13.     cy.contains('.display'42)
  14.     cy.log('**division**')
  15.     cy.contains('.operator''÷').click()
  16.     cy.contains('.button'2).click()
  17.     cy.contains('.operator''=').click()
  18.     cy.contains('.display'21)
  19.   })
  20. })

本地运行时,我将使用 npm run test:e2e 命令启动应用并打开 Cypress 。以上测试很快通过了。我们的计算器看起来加法除法运行良好。

计算器测试

正如你能从来自于 Test Runner 命令行日志信息的左侧看到的,测试覆盖率插件在运行结束时自动生成了代码覆盖率报告。报告被存储在 coverage 文件夹中,且默认有多种输出格式。

  1. coverage/
  2. lcov-report/
  3. index.html # 人类可读的 HTML 报告
  4. ...
  5. clover.xml # 面向 Clover Jenkins reporter 的覆盖率报告
  6. coverage-final.json # 纯 JSON 输出
  7. lcov.info # 面向第三方报告服务的行覆盖率

在本地运行测试时,我更喜欢打开 HTML 覆盖率报告:

$ open coverage/lcov-report/index.html

index.html 是一个展示了每个源代码文件夹覆盖率信息表格的静态页面。

覆盖率报告

提示: 将整个 coverage/lcov-report 文件夹作为一个测试产物存储在你的持续集成(CI - Continuous Integration)服务器上。然后就能在测试运行后浏览或下载报告以查看收集到的代码覆盖率了。

端到端测试是 有效的。通过一个加载整个应用并与之交互的单一测试,我们覆盖了近 60% 的源代码。更棒的是,通过点开单独的文件,我们发现了在 src/components/Calculator.vue 中那些未曾被测试到的特性。

Calculator.vue 中已覆盖/未覆盖的行

源码中高亮为红色的行正是测试中遗漏的。可以看到,虽然我们已经测试了录入数字和除法等,但仍需编写一个测试以覆盖“清理当前数字”、“改变正负号”、“设置小数点”、“乘法”等功能。代码覆盖率因此变为了编写端到端测试的向导;增加测试,直到所有红色标记的行都被干掉为止!

  1.   Calculator
  2.     ✓ computes adds and divides (1031ms)
  3.     ✓ multiplies, resets and subtracts (755ms)
  4.     ✓ changes sign (323ms)
  5.     ✓ % operator (246ms)

随着编写更多的测试,我们在应用中快速收获了覆盖率和信心。在最后一项测试中我们将覆盖仍保留了红色的 decimal () { ... } 方法。

没有被覆盖到的 Decimal 方法

以下测试键入了一个单数位数字并点击了 "." 按钮。显示结果应为 "5." 。

  1. it('decimal', () => {
  2.   cy.contains('.button''5').click()
  3.   cy.contains('.button''.').click()
  4.   cy.contains('.display''5.')
  5. })

嘿,怪了,测试失败了。

Decimal 测试失败

Cypress 测试的一个强大之处就在于其运行在真实浏览器中。让我们来调试失败的测试。在 src/components/Calculator.vue 放置一个端点。

  1. decimal() {
  2.   debugger
  3.   if (this.display.indexOf(".") === -1) {
  4.     this.append(".");
  5.   }
  6. },

打开浏览器的 DevTools 并再次运行测试。测试将运行,直到遇见应用代码中的 debugger 关键字。

调试 decimal 方法

噢,this.display 是个数字,而非一个字符串。因此 .indexOf() 并不存在且 this.display.indexOf(".") 表达式抛出了一个错误。

提示: 如果想要在任何一次 Vue 捕获错误时都让 Cypress 测试失败,在你的应用代码中做如下设置:

  1. // 从代码覆盖率中排除这些行
  2. /* istanbul ignore next */
  3. if (window.Cypress) {
  4.   // 将 Vue handler 捕获的任何错误发送给
  5.   // Cypress 顶级错误处理器以使测试失败
  6.   // https://github.com/cypress-io/cypress/issues/7910
  7.   Vue.config.errorHandler = window.top.onerror
  8. }

让我们来修复代码中的错误逻辑:

  1. decimal() {
  2.   if (String(this.display).indexOf(".") === -1) {
  3.     this.append(".");
  4.   }
  5. },

测试通过了。现在代码覆盖率报告又告诉我们条件语句的 "Else" 路径并未被考虑到。

没有 Else 路径

扩展测试以在测试中两次点击 "." 操作符,这将覆盖所有代码路径并将整个方法覆盖率变为绿色。

  1. it('decimal', () => {
  2.   cy.contains('.button''5').click()
  3.   cy.contains('.button''.').click()
  4.   cy.contains('.display''5.')
  5.   cy.log('**不会加两次**')
  6.   cy.contains('.button''.').click()
  7.   cy.contains('.display''5.')
  8. })

Decimal 测试通过

全覆盖的代码路径

现在再次运行所有测试。所有测试在 3 秒钟之内通过了。

所有测试都通过了

这些测试一起覆盖了我们整个的代码库。

完整的代码覆盖率

总结

  • 向已经使用了 Babel 转译源代码的 Vue 项目添加代码测量工具很简单。向插件列表中添加 babel-plugin-istanbul 就能在 window.__coverage__  对象中获知代码覆盖率信息。

  • 为避免减慢生产环境运行的代码,你可能只想在运行测试时测量源代码。

  • 因为运行了完整的应用,端到端测试对于覆盖大量代码非常有效。

  • @cypress/code-coverage 插件产生的代码覆盖率报告可以引导你编写测试以确保所有特性都被测试到



--End--

查看更多前端好文
请搜索 fewelife 关注公众号

转载请注明出处

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/753132
推荐阅读
相关标签
  

闽ICP备14008679号