前言
单测的意义就在于当你重构代码的时候,能够避免你的新代码不出现以前解决的问题,虽然它很重要,但是工作中总是会把它忽视,因为太耗时间了。但我们要搭建的函数库大概当然是我们积累的精华代码,它值得我们花点功夫编写单测。如果有没写过单测的同学,建议先翻翻这篇测试框架Mocha实例教程-阮一峰
所幸 vue-cli 生成的项目已经帮我们集成好了单测所需的包以及一些配置文件,我们可以直接启动 npm run test 就可以跑起测试命令。
代码
package.json
这是和测试相关的三条命令,其中我加入了一条unit:watch命令,同上面比知识去掉 --single-run参数,编写单测的时候运行这条命令能够监听文件的变动而再次运行单测,更加省力些。
...
"scripts": {
...
"unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
"unit:watch": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js",
"test": "npm run unit",
...
},
...
接着删除test/unit/specs/HelloWorld.spec.js,并添加一个test/unit/specs/formatTime.spec.js
test/unit/specs/formatTime.spec.js
import Vue from 'vue';
import formatTime from '~/formatTime.js';
Vue.use(formatTime);
describe('formatTime', function () {
// 新建一个vm对象用于实验vue.filter过滤器是否生效的
let vm = new Vue({
template: `
<p>{{ testDate | formatTime('YYYY-MM-DD') }}</p>
`,
data() {
return {
testDate: 1490774006864
};
}
}).$mount(null);
it('Vue.formatTime', () => {
// 2017年3月29日15点53分26秒
let date = vm.$data.testDate;
let dayFormat = Vue.formatTime(date, 'YYYY-MM-DD');
let secondFormat = Vue.formatTime(date, 'YYYY-MM-DD hh:mm:ss');
expect(dayFormat).to.be.equal('2017-03-29');
expect(secondFormat).to.be.equal('2017-03-29 15:53:26');
date = 123;
expect(Vue.formatTime(date, 'YYYY-MM-DD')).to.be.equal('1970-01-01');
date = '2017年3月29日';
expect(Vue.formatTime(date, 'YYYY-MM-DD')).to.be.equal('2017年3月29日');
date = '';
expect(Vue.formatTime(date, 'YYYY-MM-DD')).to.be.equal('');
date = null;
expect(Vue.formatTime(date, 'YYYY-MM-DD')).to.be.equal('');
date = void 0;
expect(Vue.formatTime(date, 'YYYY-MM-DD')).to.be.equal('');
});
it('Vue.protoType.formatTime', () => {
// 2017年3月29日15点53分26秒
let date = vm.$data.testDate;
let dayFormat = vm.$formatTime(date, 'YYYY-MM-DD');
let secondFormat = vm.$formatTime(date, 'YYYY-MM-DD hh:mm:ss');
expect(dayFormat).to.be.equal('2017-03-29');
expect(secondFormat).to.be.equal('2017-03-29 15:53:26');
});
it('Vue.filter: formatTime', () => {
expect(vm.$el.innerText).to.be.equal('2017-03-29');
});
// 遍历完所有formatTime的单测后删除vm
afterEach(() => {
vm.$el && vm.$el.parentNode && vm.$el.parentNode.removeChild(vm.$el);
});
});
由于暴露的三种调用方式都一样,于是我只给第一种方式加详细内容。我测试了一些不合法的输入,针对字符串希望能返回本字符串,null 和 undefined返回空字符串。不能被转化为正常时间的数字 123,因为 new Date(123)
为 Thu Jan 01 1970 08:00:00 GMT+0800 (CST)
。
运行结果分析
额,我就贴上关键信息吧
$ npm run test
> tt-utils@1.0.3 unit /Users/everlose/workspace/github/tt-utils
> cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run
formatTime
✓ Vue.formatTime
✓ Vue.protoType.formatTime
✓ Vue.filter: formatTime
TOTAL: 3 SUCCESS
=============================== Coverage summary ===============================
Statements : 90% ( 18/20 )
Branches : 73.33% ( 11/15 )
Functions : 100% ( 2/2 )
Lines : 88.89% ( 16/18 )
================================================================================
单测运行成功,并且所有单测结果正确了。
注意尾部的信息 Coverage summary,这是你编写的单测代码覆盖率的信息,依照上面显示的我们编写的单测只达到73.33%的分支条件,最好能想办法达到 90% 以上,你的源码才会可靠。
单测分支覆盖率补全
其一
找到源码 src/formatTime.js ,我发现似乎不能正常转化为date的这个分支我没有测试到位
var date = new Date(value);
if (Number.isNaN(date.getTime())) {
return value;
}
于是给单测 formatTime.spec.js 加入一个判断
// ...
date = 149077400686412312313;
expect(Vue.formatTime(date, 'YYYY-MM-DD')).to.be.equal(149077400686412300000);
// ...
运行后发现
=============================== Coverage summary ===============================
Statements : 95% ( 19/20 )
Branches : 80% ( 12/15 )
Functions : 100% ( 2/2 )
Lines : 94.44% ( 17/18 )
其二
在分析源码得知其实还可以传入毫秒数,我的单测里缺没有传
var o = {
// ...
'S': date.getMilliseconds() // millisecond
};
于是单测里加上
// ...
let secondFormat = Vue.formatTime(date, 'YYYY-MM-DD hh:mm:ss:S');
expect(secondFormat).to.be.equal('2017-03-29 15:53:26:864');
// ...
运行后发现
=============================== Coverage summary ===============================
Statements : 95% ( 19/20 )
Branches : 86.67% ( 13/15 )
Functions : 100% ( 2/2 )
Lines : 94.44% ( 17/18 )
额,暂时我也不知道该怎么把 Branches 再提升一下了,留个坑后面回来补。
单测代码优化
大家或许留意到了 formatTime.spec.js 最后一段
// 遍历完所有formatTime的单测后删除vm
afterEach(() => {
vm.$el && vm.$el.parentNode && vm.$el.parentNode.removeChild(vm.$el);
});
每次增删一个 vm 都这么麻烦,那是该抽象出来了,所以我这里致敬了一下element ui 源码。
追加一份 test/unit/utils.js
import Vue from 'vue';
let id = 0;
const createElm = function () {
const elm = document.createElement('div');
id += 1;
elm.id = `app${id}`;
document.body.appendChild(elm);
return elm;
};
/**
* 回收 vm
* @param {Object} vm
*/
exports.destroyVM = function (vm) {
vm.$el && vm.$el.parentNode && vm.$el.parentNode.removeChild(vm.$el);
};
/**
* 创建一个 Vue 的实例对象
* @param {Object|String} Compo 组件配置,可直接传 template
* @param {Boolean=false} mounted 是否添加到 DOM 上
* @return {Object} vm
*/
exports.createVue = function (Compo, mounted = false) {
if (Object.prototype.toString.call(Compo) === '[object String]') {
Compo = {
template: Compo
};
}
return new Vue(Compo).$mount(mounted === false ? null : createElm());
};
接着修改 test/unit/spec/formatTime.js
// ...
import { createVue, destroyVM } from '../utils';
//...
describe('formatTime', function () {
let vm = createVue({
template: `
<p>{{ testDate | formatTime('YYYY-MM-DD') }}</p>
`,
data() {
return {
testDate: 1490774006864
};
}
}, true);
// ...
afterEach(() => {
destroyVM(vm);
});
}
结论
看懵逼了么?正是因为编写单测的过程非常繁琐,还得思考自己以前编写的时候压根没有想到的分支条件,可能写单测的时间还会多于编写源码的时间。
本篇所有代码在 tag v0.0.4 中, 给你参考。
上一篇:npm包发布篇
下一篇:md示例文档编写篇