查看原文
其他

【第1664期】Vue组件库工程探索与实践之单元测试

FransLee 前端早读课 2019-07-10

前言

《Vue组件库工程探索与实践》系列文章第三篇来了,这篇聊聊组件库单元测试和持续集成功能的实现。今日早读文章由@京东用户体验设计部(JDC) @FransLee投稿分享。

正文从这开始~~

单元测试是软件工程领域的一个重要概念,指对软件中的最小可测试单元进行检查和验证,它是代码正确性验证的最重要的工具。单元测试也是组件库实现自动化测试与集成的基础。

单元测试会封闭执行最小化单元的代码,使添加新功能和追踪问题更容易。对代码进行单元测试有很多好处:

  • 可节省手动测试的时间

  • 有助于减少开发新特性时产生的副作用

  • 有利于改进设计和重构

  • 有助于提升大团队中复杂基础代码的可维护性


前端小伙伴们对单元测试多多少少都有一定的了解,但真正有实践经验的并不太多。我想可能与业务项目工期紧张、需求变化比变脸还快、对单元测试的意义认识不够等方面因素有关。当然,也不是所有项目都需要单元测试。

程序猿是这么死的

不过,随着时间的推移,阅历的累积,或许有一天你会真正意识到它的价值,就像李宗盛的歌。

少年不听李宗盛,听懂已是不惑年

对于组件库这种可复用的公共代码来说,其对可靠性、可维护性的要求较普通业务类项目代码更高,引入单元测试是十分必要的。这里分享一下我们在Vue组件库的单元测试方面的探索与实践。

限于篇幅,本文重点谈Vue组件库的单元测试功能及实现,不会过多介绍单元测试基础知识。

首先我们需要明确,Vue组件库的单元测试,主要还是针对其中的Vue组件。

Vue Test Utils

为了方便对 Vue 单文件组件进行测试,Vue.js 官方提供了测试工具库 Vue Test Utils,它提供了一系列方法用于测试Vue组件。

Vue Test Utils 通过把组件隔离挂载,模拟必要的输入 (prop、注入和用户事件) 和对输出 (渲染结果、触发的自定义事件) 的断言来测试 Vue 组件。被挂载的组件会返回到一个包裹器内,而包裹器会暴露很多封装、遍历和查询其内部的 Vue 组件实例的便捷的方法。此外,Vue Test Utils 还提供了模拟用户交互的相关方法。

可见,Vue Test Utils 是 Vue 组件单元测试的必备工具,需要安装。

$ npm install --save-dev @vue/test-utils

测试运行器

测试运行器 test runner 即运行测试的程序,也就是我们通常所说的测试框架。Vue Test Utils 是与测试运行器无关的,主流的测试运行器都支持。

官方推荐两个测试运行器:Jest 和 mocha-webpack。

Jest 是 Facebook 开源的一套 JavaScript 测试框架, 它自动集成了断言、JSDOM、覆盖率报告等几乎所有测试工具,功能相当强大。它所需的配置是最少的,不过需要一个能够将Vue单文件组件导入到测试中的预处理器。Vue.js官方提供了 vue-jest 预处理器来处理最常见的单文件组件特性,但仍不是 vue-loader 全部的功能。

而 mocha-webpack 是知名前端测试框架 Mocha 与 webpack 的一个包裹器。下面这行命令就是它的大致工作原理:先对文件进行 webpack 编译,再用 Mocha 对编译后的文件进行测试。当然,其中还包含很多优化工作。

$ webpack test.js output.js && mocha output.js

因此我们能够通过 webpack + vue-loader 得到完整的Vue单文件组件支持。mocha-webpack 的配置比 Jest 复杂很多。

mocha-webpack 的 2.0.0-beta.0版支持 webpack 4

为了获得完整的Vue单文件组件支持,我们在 NutUI 2.0 项目中选择了 mocha-webpack 测试运行器。

浏览器环境

Vue.js 官方的测试工具库 Vue Test Utils 依赖浏览器环境,而启动真实的浏览器是非常复杂的,因为涉及到不同的平台和版本,兼容性与稳定性不易处理。从另一个角度看,自动测试通常不需要浏览器的用户界面,使用无头浏览器(headless browsers)就能满足需求。

无头浏览器是一种没有图形用户界面的网页浏览器,可以在类似于流行的 Web 浏览器的环境中提供对网页的自动控制,通过命令行界面或使用网络通信来执行。它们能以与浏览器相同的方式渲染和理解 HTML,包括页面布局、颜色、字体选择、JavaScript 与 Ajax 的执行等等。通常用于 Web 的自动化测试等场景。

当下比较流行的无头浏览器有 JSDOM、Puppeteer、Selenium 等等,我们的项目中选择了 JSDOM 在 Node 虚拟浏览器环境运行测试。

$ npm install --save-dev jsdom jsdom-global// 在测试的配置文件中
require
('jsdom-global')()

断言库

“断言”就是判断代码的实际执行结果与预期结果是否一致,如果不一致就抛出一个错误。所有的测试用例都应该包含至少一条断言。它是编写测试用例的关键。

比如下面这条断言,它的含义是调用 add(1,1) 的结果应该等于2。

expect(add(1,1)).to.be.equal(2);

断言功能由断言库来实现。Jest 框架内置了断言库 expect,而 Mocha不包含断言库。所以选择使用 Mocha框架的时候,我们还需要额外安装一个断言库。

expect 断言风格(如上面的栗子)很接近自然语言,NutUI 2.x 项目的断言库选用的也是 expect 。除了 expect,流行的断言库还有 chai、assert、should 等。

$ npm install --save-dev expect

在测试的配置文件中引入 expect,并把它挂到全局对象上,当然也可以在每个测试文件中分别导入。

global.expect = require('expect')

测试环境配置

在确定了测试运行器使用 mocha-webpack,浏览器环境使用 JSDOM,断言库使用 expect 之后,我们就可以开始在组件库脚手架中进行测试工具的安装和配置了。

首先安装测试所需依赖。

$ npm install --save-dev @vue/test-utils mocha mocha-webpack@2.0.0-beta.0 expect jsdom jsdom-global

然后,在项目根目录下新建 test/setup.js 目录及文件,用来设置测试所需的全局环境。我们在该文件中引入 JSDOM 和 expect。

test/setup.js
require('jsdom-global')();
global
.expect = require('expect');

接着,在 package.json文件中新增一个测试脚本。

package.json
{
"scripts": {
"test": "mocha-webpack --webpack-config webpack.test.conf.js --require test/setup.js src/packages/*/__test__/**.spec.js"
}
}
  • —webpack-config 指定了测试使用的 webpack 配置文件 webpack.test.conf.js。该文件与生产环境配置文件有些差异,下文会细说。

  • —require 标识确保文件 test/setup.js 在测试之前运行,因此我们可以在该文件中设置测试所需的全局环境。

  • 最后一个参数是该测试包所涵盖的所有测试文件的聚合。

测试覆盖率

测试覆盖率是对测试完全程度的度量,是由测试需求、测试用例的覆盖或已执行代码的覆盖表示的。

一个统计 JavaScript 单元测试覆盖率的知名工具是 istanbul,它以土耳其最大城市伊斯坦布尔命名,因为土耳其地毯世界闻名,而地毯是用来覆盖的。

伊斯坦布尔

我们来看下在 mocha-webpack框架下,如何使用 istanbul统计测试覆盖率。

首先,我们需要安装 nyc和 istanbul-instrumenter-loader:

$ npm install --save-dev nyc istanbul-instrumenter-loader
  • nyc: istanbul的命令行工具。

  • istanbul-instrumenter-loader: 使用钩子(hooks)包装代码以在代码执行时跟踪覆盖率的loader。


接下来,修改 package.json文件 scripts字段下 test脚本,同时增加 nyc的相关配置项:

package.json
{
...
"scripts": {
"test": "cross-env NODE_ENV=test nyc --reporter=lcov --reporter=text mocha-webpack --webpack-config build/webpack.test.conf.js --require test/setup.js src/packages/*/__test__/**.spec.js"
...
},
"nyc": {
"include": [
"src/packages/**/*.vue"
],
"instrument": false,
"sourceMap": false
},
...
}

test脚本中 —reporter=lcov —reporter=text是配置 nyc同时输出lcov (lcov.info + html)和文本形式的覆盖率报告。

而 scripts字段下方 nyc的配置项中:

  • include 用来指定被测试源文件的位置。

  • 禁用 nyc 的 instrument 和 sourceMap 选项,因为这些工作应该由 loader 负责。

接下来需要把 istanbul-instrumenter-loader加到 webpack 的配置文件中。上文提到,我们给单元测试工作指定的 webpack 配置文件是 webpack.test.conf.js,它与生产环境的配置文件有些差异,我们先把生成环境的配置文件 webpack.prod.conf.js 导入,再把这些差异 merge 进去。

webpack.test.conf.js
const path = require('path');
const prodConf = require('./webpack.prod.conf.js');
const merge = require('webpack-merge');

module
.exports = merge(prodConf, {
module
: {
rules
: [
{
test
: /\.(js|ts)/,
use
: {
loader
: 'istanbul-instrumenter-loader',
options
: { esModules: true }
},
include
: path.resolve(__dirname, '../src/packages/')
},
],
},
devtool
: 'inline-cheap-module-source-map',
externals
: [require('webpack-node-externals')()]
});
  • 我们给 js/ts 类型的文件新增了 loader:istanbul-instrumenter-loader,要注意的是,该 loader 必须放在数组的第一个,以确保它最后一个应用。

  • sourceMap 在 mocha-webpack中必须通过内联方式获取,所以 devtool 选项推荐的值为 inline-cheap-module-source-map 。

  • 为了提升测试的启动速度,我们可以通过 webpack-node-externals 外置所有的 NPM 依赖。

至此,整个项目的自动化测试功能及覆盖率统计配置基本完成,我们在 src/packages/ 目录下的每个组件的目录下新建一个test目录,该目录下的单元测试文件以 .spec.js 作为扩展名。

目录结构

准备好组件的单元测试文件之后,在终端执行 npm test 即可启动测试。测试的过程中,终端会展示每个测试用例的测试结果。

单元测试

测试结束之后,终端会以文本形式展示出本次测试的覆盖率报告,同时 /coverage目录下也会生成测试覆盖率报告文件(Icov.info+html)。

测试覆盖率

持续集成

持续集成(Continuous Integration, CI)是一种软件开发实践,即每次代码的集成都通过自动化的构建(包括编译/发布/自动化测试)来验证,从而尽早的发现集成错误。也就是说,只要代码有变更,就自动运行构建和测试,确保符合预期后,再将新代码集成到主干。它的核心措施是,在代码集成到主干之前,必须通过自动化测试。

持续集成的意义在于:

  • 每次代码提交都进行自动构建和测试,有助于尽早的发现问题和解决问题,减少风险。

  • 自动化的构建和测试可以减少人重复的工作,节约时间,降低成本。

  • 有助于提升项目质量。

Travis CI 是在线托管的 CI 服务,使用 Travis 来进行持续集成。它对于开源项目是免费的,支持绑定 Github 上面的项目。只要有新的代码,就会自动抓取。然后,提供一个运行环境,自动进行构建和测试,还支持部署到服务器。

我们来看下 Travis CI的基本用法。

首先,访问 Travis CI 官网并使用 Github 账户登录。
然后,点击其网站右上角的个人头像,网页会列出 Github 上我们和我们所在的组织的所有代码仓库。打开需要进行 CI 的仓库右侧的开关即可。

激活仓库

接下来,我们需要在这个代码仓库的根目录放置一个名为 .travis.yml 的 Travis CI 配置文件。NutUI 2.x项目的配置文件内容如下:

sudo: required
language
: node_js
node_js
:
- '8'
script
:
- npm test
- npm run coveralls
  • sudo: required 表示需要 sudo 权限。

  • language: node_js 指定运行环境为 Node 。

  • node_js 字段用来指定 Node 版本。

  • script 字段用来指定构建或者测试的脚本。

Travis 的运行流程包含两个阶段:install(安装依赖)和 script(执行脚本)。对于 Node 项目来说,install和script阶段都有默认脚本,如不需要修改,可以省略不写。

  • install的默认脚本是:npm install 。

  • script的默认脚本是:npm test 。

Travis CI 还支持自动部署,但不是必须的。我们的组件库没有用到,这里不多说。

配置完成之后,每次往 Github 的该仓库 push 代码,都会触发 CI。登录 Travis CI网站可以看到结果。

NutUI某次自动构建结果

我们还可以从 Travis CI 网站获取一个关联该仓库 CI 结果的徽标,放在我们项目的 README.md 文件中,这样即便不登录 Travis CI 网站,我们也可以通过该徽标知晓 CI 结果了。

CI结果徽标

测试覆盖率上报

查看 NutUI 的 README.md文件,会发现除了上文提到的CI结果徽标,还有一个展示测试覆盖率的徽标。

这个徽标是通过 coveralls.io获取的。coveralls.io 提供测试覆盖率的追踪服务,我们可以把测试覆盖率报告上报给 coveralls.io ,它会基于接收到的数据生成一个测试覆盖率徽标。

coveralls.io 支持 Github 上的项目,也可以与 Travis CI集成。关于它的具体使用,限于篇幅,这里就不展开了,有兴趣的小伙伴可以阅读其官方文档。

好了,这篇文章先聊到这里。如果对具体实现细节感兴趣,可以查看 NutUI 2.x 项目的源码,也欢迎各位老铁Star,赠人Star,手有余香~

链接

  • NutUI : https://github.com/jdf2e/nutui

  • Vue Test Utils:https://vue-test-utils.vuejs.org/

  • expect 文档:https://jestjs.io/docs/en/expect.html

  • Travis CI:https://www.travis-ci.org/

  • coveralls:https://coveralls.io/

  • 持续集成服务 Travis CI 教程 : http://www.ruanyifeng.com/blog/2015/12/a-mocha-tutorial-of-examples.html


本系列其他文章


【第1585期】Vue组件库工程探索与实践之构建工具


【第1642期】Vue组件库工程探索与实践之按需加载


为你推荐


【第1129期】对vue.js单文件(.vue)进行单元测试

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存