最近在搭建一个项目的前端开发环境,准备趁此把一些没用过的东西尝试下,比如:单元测试。
使用vue-cli可以直接生成一个包含unite2e测试的开发环境,不过还是需要去了解其中的组织结构、配置和模块的使用等。

准备

没有一点相关经验,先把vue-cli生成后的test/unit目录拿到自己的项目中。

test 目录结构:

└─ test
└─ unit
├─ specs
├─ .eslintrc
├─ index.js
└─ karma.conf.js

之后在package.json中找到karma相关的模块。

  • karma 一个 JavaScript 测试运行器,其在测试中的作用相当于开发构建中使用的webpack
  • karma-webpack 连接karmawebpack的桥梁。不经过webpack编译命令是文件是无法独立运行的,karma需要了解你的webpack配置,决定如何处理你的测试文件。
  • karma-phantomjs-launcherphantomjskarma中的启动器,由此引出了phantomjsPhantomJS 是一个没有界面的 “浏览器” ,内置了 JavaScript API,支持各种Web标准:DOM 处理、CSS 选择器、JSON、Canvas 和 SVG 等。在查找相关资料时,也发现了其他的常规浏览器launcher,比如:Chrome、Firefox、Safari、IE 等,以应对不同浏览器或多浏览器的测试需求。见Browsers
  • karma-sourcemap-loader Karma插件,生成文件的sourcemap
  • karma-mocha 让你在 karma 中使用 Mocha 测试框架的插件,使用时还需要安装前置依赖mocha
  • karma-sinon-chai 让你在karma中使用sinon-chai断言库的插件,前置依赖有sinon-chaisinonchai…😕
  • karma-spec-reporter 用于将测试结果显示到控制台。
  • karma-coverage 生成代码覆盖率。

其中要使用karma-phantomjs-launcher,需要先安装PhantomJS,通过phantomjs-prebuilt安装。

看到这一大堆依赖,感觉面前又是一个不亚于 Webpack 的技术栈。之所以去了解每个模块的用处,就是为了在学习的时候直插心脏,忽略那些不重要的模块。
以上来说,Karma 是基本的运行器,需要了解其配置和使用。测试框架 Mocha 和断言库 sinon-chai 类似于开发中使用的 Lodash,提供一系列用于测试的工具函数。剩下的按示例集成就好。

安装

整合一条命令。

npm i -D karma karma-webpack phantomjs-prebuilt karma-phantomjs-launcher karma-sourcemap-loader mocha karma-mocha sinon chai sinon-chai karma-sinon-chai karma-spec-reporter karma-coverage

PhantomJS 安装比较慢,也可以去其官网下载,并手动配置环境变量

安装好了,先跑一下看看是否安装正确,到package.jsonscripts中注册一个命令:"unit": "karma start test/unit/karma.conf.js --single-run",然后运行。

npm run unit

然后不出意料的报错了,只是少装了一个模块isparta-loader,是一个代码覆盖工具,继续安装后运行OK。

vue-unit-1
vue-unit-1

配置

karma.conf.js 文件内容分为两部分。
module.exports 以上是对 Webpack 配置的处理,像webpack.dev.confwebpack.prod.conf那样,在不同的环境里使用不同的配置。后一部分就是对 Karma 的配置了。

module.exports = function (config) {
config.set({
// 要启动的测试浏览器
browsers: ['PhantomJS'],
// 测试框架
frameworks: ['mocha', 'sinon-chai'],
// 测试报告处理
reporters: ['spec', 'coverage'],
// 要测试的目标文件
files: ['./index.js'],
// 忽略的文件
exclude: [],
// 预处理文件
preprocessors: {
'./index.js': ['webpack', 'sourcemap']
},
// webpack
webpack: webpackConfig,
webpackMiddleware: {
noInfo: true
},
// Coverage options
coverageReporter: {
dir: './coverage',
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text-summary' }
]
},
// true: 自动运行测试并退出
// false: 监控文件持续测试
singleRun: true,
// 以下是 vue-cli 没有生成的一些配置
// 文件匹配的起始路径
// basePath: '',
// 服务器端口
// port: 9876,
// 输出着色
// colors: true,
// 日志级别
// LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
// logLevel: config.LOG_INFO,
// 监控文件更改
// autoWatch: true,
// 超时处理,6s内没有捕获浏览器将终止进程
// captureTimeout: 6000
})
}

其中对于测试文件入口有点难以理解,仅仅是一个脚本文件,而不是路径匹配,也就是配置文件同级的那个index.js,只有 5 行代码。

Function.prototype.bind = require('function-bind')
const testsContext = require.context('./specs', true, /\.spec$/)
testsContext.keys().forEach(testsContext)
const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
srcContext.keys().forEach(srcContext)

看起来如果使用了高版本的 NodeJs 第一行代码是可以去掉了。

  • testsContext匹配的是specs目录,里面是存放的是测试用例;
  • srcContext匹配的是src目录,从正则上看是除了main.js以外的所有文件。

也就是说,也可以通过脚本文件这种方式设置匹配的目标文件。

实践

现在看起来一切妥当了,可以开始写测试用例了。

把以上步骤又重复了一遍,集成到了一个自己写的 Demo 项目中,然后运行测试。

vue-unit-2
vue-unit-2

什么鬼,找不到 less 变量。

为了验证不是配置问题,我在 vue-cli 生成项目的组件中使用 less 写了个样式,运行测试出现了一样的问题。
最后找到了这条issue
翻译了半天,感觉是匹配文件的问题。

const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)

原来的正则会匹配除了main.js以外的所有文件,所以样式文件也会被匹配上。使用 less 时一般只有一个变量文件,然后从一个 less 出口文件中解析,而单元测试本身做的工作是单文件测试,单个 less 文件肯定是找不到变量的。
不过样式本身是不会对逻辑产生影响的,可以不参与测试,干脆直接锁定目标文件后缀,也就是 vue 组件。

修改如下:

const srcContext = require.context('../../src', true, /\.vue$/)

刚翻过一座山,又遇到一条河。

vue-unit-3
vue-unit-3

看起来是因为 API 不兼容,PhantomJS 中还没有集成 Promise。

vue-unit-4
vue-unit-4

为了避免相似的问题,索性把 launcher 换掉了,用karma-chrome-launcher
安装后,更改 Karma 配置中的browsers属性值为['Chrome'],测试运行成功。

vue-unit-5
vue-unit-5

有一些青色的 LOG,第一条使用 Vue 的应该都已经熟悉了,是浏览器控制台输出的 LOG,所以其他的也是浏览器的日志了。

第 1、3、4 条 LOG 是有一个指令没有找到,第 2 条是关于路由的。指令和路由的引入是在项目入口的 js 文件内,可以预知如果我使用了其他类别的 Vue 插件也会报出 ERROR。
还是有问题要解决啊!😒

测试用例

初次接触测试框架和断言,对于组件的测试用例也还在摸索中。
从例子上看,大概思路如下:

  1. 引入一个组件;
  2. 创建一个用于测试的 Vue 实例,然后组件挂上去;
  3. 对实例进行模拟操作,然后断言期望值。
import Vue from 'vue'
import SearchView from 'src/components/SearchView'
describe('SearchView.vue', () => {
it('Input is normal', () => {
const vm = new Vue({
el: document.createElement('div'),
render: (h) => h(SearchView)
})
vm.key = '曾经的你'
vm.$nextTick(() => {
expect(vm.$el.querySelector('.m-key').textContent).to.equal('曾经的你')
})
})
})

实际的项目中,针对复杂的逻辑或者异步操作,测试一定不会这么简单。😅
下一步准备找一些 Vue 的开源项目,学习一下其中的测试用例写法。

End!