2017年8月

关于 Node.js 测试

单元测试是大型 Web 项目必不可少的部分. 测试和其他部分一样, 从一开始就要设计好实现, 否则可能就可能不断给自己买埋坑. Node.js 的测试一般由测试框架和断言库两部分组成.

测试框架用的比较多的是 mocha, 我也用过一阵子的 ava. mocha 大家可能都比较熟悉, ava 可能实际使用的不多. 这两个其实差别也有点大, ava 的话是每个文件都开一个进程执行, 并且文件内的测试默认是并发执行, 听起来就很快. ava 的写法也比 mocha 简洁很多. ava 自带断言库, 并且支持异步断言(assert 通过插件也可以实现, 但没有 ava 简洁). 但 ava 对 typescript 支持不好, 需要 TypeScript 文件先编译成 JavaScript 文件后才能进行测试. 而 mocha 就不存在这个问题.

就如同 ava 官网介绍的, 面向未来的测试框架. 我个人还是非常看好 ava 的, 一旦它支持 TypeScript 了, 我立马就会转过来.

断言库的话一开始实习公司用的是 should. 我不知道为什么很多人喜欢用 should, api 长又难记. 我对它一直都很抗拒, 后面公司的断言库都是使用 assert(chai) 来进行. 除了 should 和 assert 之外还有一个比较有名的断言库就是 expect. 萝卜青菜各有所爱, 我个人偏爱是 assert, 现在主要是使用 power-assert, api 非常简洁, 而且错误提示做的一级棒.

// assert
assert.lengthOf(foo, 3)
// expect
expect(foo).to.have.length(3)
// should
foo.should.have.length(3)
// power-assert
assert(foo.length === 3)

正如 power-assert 官网说的, NO API is the best API. power-assert 简直不要太好, 特别是当断言错误时他的提醒更是极致. 就像下面这样:

    assert(types[index].name === bob.name)
           |    ||      |    |   |   |
           |    ||      |    |   |   "bob"
           |    ||      |    |   Person{name:"bob",age:5}
           |    ||      |    false
           |    |11     "alice"
           |    Person{name:"alice",age:3}
           ["string",98.6,true,false,null,undefined,#Array#,#Object#,NaN,Infinity,/^not/,#Person#]

    --- [string] bob.name
    +++ [string] types[index].name
    @@ -1,3 +1,5 @@
    -bob
    +alice

反观 assert, 原生的 assert 的问题在于功能太少. 所以一般会使用 chai. 而 expect 和 should, 总之我是写不下去.

因为我现在都是在写 TypeScript. 所以现在选定的测试套件是 mocha + power-assert.

测试遇到的问题

我个人参与了很多后端项目的开发, 特别是前期开发部分. 早期的测试文件也是由我来写, 我也认真思考过如何写一个比较清晰可维护的测试目录结构. 这过程也踩了不少坑, 这里简单介绍下.

测试前置数据

很多业务代码的测试是需要一些前置数据的, 一开始, 前置数据我们都是直接在该测试文件的 before 钩子完成, 然后在 after 钩子进行销毁. 但随着项目越来越大, 这种方式的弊端开始显现, 很多文件其实 before 部分是大体一致的, 这样对于代码修改起来就十分不便, 另外频繁的创建销毁也增加了开销.

很自然的就可以想到, 可以有一个全局的钩子, 批量创建测试需要的所有数据, 然后最后再统一销毁. 一开始没有了解到 mocha 的全局钩子, 我们是 mocha 运行一个文件, 该文件导入其他测试文件来进行的. 但其实 mocha 本身有全局钩子的功能, 利用全局钩子就可以干这种事情了. 这样就解决了上述的问题.

单进程测试应用奔溃

但其实还有一个一直被大多数人忽略的问题, 一般我们也是在测试里面启动项目, 然后对项目进行测试, 如果项目运行奔溃, 我们的测试进程也就跟着奔溃了. 结果就是 after 钩子无法被执行. 数据库有残余的测试数据插入. 对后面的再次测试也可能会产生影响.

要解决这个问题. 只能开多进程解决. 我们可以利用 cluster 来实现. mocha 自带延迟执行的功能, 我们需要延缓 mocha 的运行否则其全局钩子可能出问题.

测试覆盖率和持续集成

关于测试覆盖率报告, 我一直都是使用 nyc 来进行. nyc 的优点在于不需要进行额外配置, 兼容 babel, typescript. 测试覆盖统计也很准确, 使用起来非常简单. 并且也可以自定义测试覆盖率报告的输出格式. nyc 输出测试覆盖报告后我们可以再通过 codecov 来上传测试覆盖率报告.

持续集成方面, travis-ci 和 circle-ci 我都使用过. 简单的来说, travis-ci 用的比较多, 书写格式比较简洁, 用法比较简单, 文档也很容易看, 但测试速度贼慢. 而 circle-ci 支持多种格式, 支持 docker, 测试速度快, 但配置起来可能比较麻烦. 我目前基本都在使用 circle-ci, 因为 travis-ci 实在慢并且好像是要收费??

- 阅读剩余部分 -