实现一个自己定制的函数库-单测编写篇

494 阅读3分钟
原文链接: github.com

前言

单测的意义就在于当你重构代码的时候,能够避免你的新代码不出现以前解决的问题,虽然它很重要,但是工作中总是会把它忽视,因为太耗时间了。但我们要搭建的函数库大概当然是我们积累的精华代码,它值得我们花点功夫编写单测。如果有没写过单测的同学,建议先翻翻这篇测试框架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示例文档编写篇