使用 karma/mocha 全家桶为 Vue 组件编写单元测试
编写单元测试可以大大提高项目的稳定性和内心的安全感。对于功能点稳定、需长期迭代的项目,编写单元测试可以有效的减少维护成本,降低 Bug 率。最近在为公司内部的 Vue 组件库添加单元测试,配置测试环境、编写测试用例花了一些时间,略作整理。
1. 整理安装 Karma + mocha + sinon + chai 全家桶
整理一下配置测试环境所需要的依赖:
- karma //test runner,提供测试所需的浏览器环境、监测代码改变自动重测、整合持续集成等功能
- phantomjs-prebuilt //phantomjs,在终端运行的浏览器虚拟机
- mocha //test framework,测试框架,运行测试
- chai //assertion framework, 断言库,提供多种断言,与测试框架配合使用
- sinon //测试辅助工具,提供 spy、stub、mock 三种测试手段,帮助捏造特定场景
- karma-webpack //karma 中的 webpack 插件
- karma-mocha //karma 中的 mocha 插件
- karma-sinon-chai //karma 中的 sinon-chai 插件
- sinon-chai //karma 中的 chai 插件
- karma-sourcemap-loader //karma 中的 sourcemap 插件
- karma-phantomjs-launcher //karma 中的 phantomjs 插件
- karma-spec-reporter //在终端输出测试结果
- istanbul-instrumenter-loader //代码覆盖率统计工具 istanbul
- karma-coverage-istanbul-reporter //代码覆盖率报告产出插件
官方示例中是使用 karma-coverage
来统计代码覆盖率的,不过很遗憾用来测试 Vue 组件输出结果不太正常,折腾一番无果,参照其他开源项目最终替换为了 istanbul 。
全家桶安装一波:
|
|
2. 配置 karma
按照 karma 的文档,运行:
|
|
先后选择使用的测试框架、是否使用 require.js、浏览器环境、测试脚本存放位置、是否有需要 ignore 的文件,等等。很简单,选择完毕之后,该项目根目录下生成名为 karma.conf.js
文件。
接下来就是设置 karma 的各项插件的配置:
|
|
3. 规划目录结构
|
|
项目源代码均处于 src 和 packages 两个文件夹下,且 src 文件夹下存在一个总入口文件,其中引入了 src 与 packages 下的全部模块。
测试相关文件均放在 test/unit 文件夹下,总入口文件为 index.js,各个组件的单测文件分别为 组件名.spec.js。
karma 配置文件 karma.conf.js 放置于项目根目录下。
根据 istanbul-instrumenter-loader
文档的说明,测试总入口文件 index.js 内容如下:
|
|
4. 编写 Vue 组件的单元测试
经过上面三步,karma 全家桶已经配置完毕,现在只需在根目录下运行:
|
|
即可运行 karma,并自动运行所有 test/unit/*.spec.js 的单测文件,同时监测代码改动,自动重跑测试。不过现在还没有单测文件,我们来写一个。
举例一个标准的 Vue 组件:
根据 vue 单元测试相关的官方文档,我们知道可以这样在测试环境下测试单独组件:
|
|
使用 Vue.extend 方法可以创建出一个组件实例,还可以直接将 prop 数据传进去。我们将这个方法封装下:
|
|
然后编写 Button 组件的单元测试:
|
|
组件创建完毕之后,结合断言库对情况进行测试。例如:当给 Button 组件传入 prop:type = ‘gray’ 时,实例的 DOM 上应该会有 ‘wd-button-gray’ 这个 Class,于是可用断言库进行判断:
|
|
当给 Button 组件传入 prop:disabled = true 时,实例的 DOM 上应该有一个叫 disabled 的 Attribute。断言:
|
|
以此类推,逐步测试各个功能点是否工作正常。如果测试未通过,即行为与预期不服,断言失败,会报错:
如果测试通过,终端结果如下:
src 和 packages 中源码很多,测试只写了一点点,可以看到 Coverage summary 即代码覆盖率很低。
mocha 与 chai 的语法这里不再赘述,编写测试用例时需多多查看文档。
5. 一些小 Tips
mocha 中涉及异步操作的测试,要使用 done 函数:
|
|
涉及到 Vue.nextTick
和 setTimeout
等异步操作的测试,要使用 done()
来标记完成时间点。
mocha 提供 beforeEach
afterEach
等方法帮忙做些杂活;比如我想每次测试完组件的其中一个用例之后,清空一下 body 节点,防止留下残余 DOM 影响后续的操作:
|
|
将测试任务集成进 npm scripts:
|
|
为项目中的3个组件添加了单元测试,暂时写了40个测试用例,基本覆盖了文档上的全部功能点。这之后如果对组件做重构/ bug 修复/加新功能时,就不需要担心是否会影响老功能,也不需要自己手动 check,直接跑 npm run test
测试看结果就可以了。安全感 up。
在项目根目录的 coverage 文件夹内可以看到输出的 html 格式代码覆盖率报告:
点击文件可以看到更详细的说明:哪行代码测到了,哪行代码没有测到:
图中标红的地方即为没被测到的代码,有些是在 if 分支内,有的在 watch 里,没有被执行过。另外 phantomJS 对于模拟鼠标键盘事件的能力并不强,没有提供相关的 api,所以图中的有关鼠标拖拽的事件很难模拟,没能进行测试。这些测试更适合用 E2E 测试而非单元测试来做。
另外代码覆盖率有些地方算得并不准确,比如 Button/src 下的 Button.vue
组件,因为比较简单,没有 methods,只有几个 prop,被判断成了 0% 的覆盖率(实际上 Button 组件的5个用例已经把每个 prop 都测过一遍了)。希望未来这个工具能更新的更加准确一些。
其他组件的单测日后逐渐补上。