原文连接:BlueSun | 在Nodejs中贯彻单元测试javascript
在团队合做中,你写好了一个函数,供队友使用,跑去跟你的队友说,你传个A值进去,他就会返回B结果了。过了一会,你队友跑过来讲,我传个A值却返回C结果,怎么回事?你丫的有没有测试过啊?html
你们一块儿写个项目,不免会有我要写的函数里面依赖别人的函数,可是这个函数到底值不值得信赖?单元测试是衡量代码质量的一重要标准,纵观Github的受欢迎项目,都是有test文件夹,而且buliding-pass的。若是你也为社区贡献过module,想更多人使用的话,加上单元测试吧,让你的module值得别人信赖。html5
要在Nodejs中写单元测试的话,你须要知道用什么测试框架,怎么测试异步函数,怎么测试私有方法,怎么模拟测试环境,怎么测试依赖HTTP协议的web应用,须要了解TDD和BDD,还有须要提供测试的覆盖率。java
本文的示例代码会备份到 Github : unittest-demonode
测试框架git
断言库github
需求变动web
异步测试shell
异常测试express
测试私有方法
测试Web应用
覆盖率
使用Makefile把测试串起来
持续集成,Travis-cli
一些观点
彩蛋
整理
Nodejs的测试框架还用说?你们都在用,Mocha。
Mocha 是一个功能丰富的Javascript测试框架,它能运行在Node.js和浏览器中,支持BDD、TDD、QUnit、Exports式的测试,本文主要示例是使用更接近与思考方式的BDD,若是了解更多能够访问Mocha的官网
Mocha的BDD接口有:
describe()
it()
before()
after()
beforeEach()
afterEach()
npm install mocha -g
模块具有limit方法,输入一个数值,小于0的时候返回0,其他正常返回
exports.limit = function (num) { if (num < 0) { return 0; } return num; };
lib
,存放模块代码的地方
test
,存放单元测试代码的地方
index.js
,向外导出模块的地方
package.json
,包描述文件
var lib = require('index'); describe('module', function () { describe('limit', function () { it('limit should success', function () { lib.limit(10); }); }); });
在当前目录下执行mocha
:
$ mocha ․ ✔ 1 test complete (2ms)
上面的代码只是运行了代码,并无对结果进行检查,这时候就要用到断言库了,Node.js中经常使用的断言库有:
should.js
expect.js
chai
使用should
库为测试用例加上断言
it('limit should success', function () { lib.limit(10).should.be.equal(10); });
需求变动啦: limit
这个方法还要求返回值大于100时返回100。
针对需求重构代码以后,正是测试用例的价值所在了,
它能确保你的改动对原有成果没有形成破坏。
可是,你要多作的一些工做的是,须要为新的需求编写新的测试代码。
lib库中新增async函数:
exports.async = function (callback) { setTimeout(function () { callback(10); }, 10); };
测试异步代码:
describe('async', function () { it('async', function (done) { lib.async(function (result) { done(); }); }); });
使用should提供的Promise断言接口:
finally
| eventually
fulfilled
fulfilledWith
rejected
rejectedWith
then
测试代码
describe('should', function () { describe('#Promise', function () { it('should.reject', function () { (new Promise(function (resolve, reject) { reject(new Error('wrong')); })).should.be.rejectedWith('wrong'); }); it('should.fulfilled', function () { (new Promise(function (resolve, reject) { resolve({username: 'jc', age: 18, gender: 'male'}) })).should.be.fulfilled().then(function (it) { it.should.have.property('username', 'jc'); }) }); }); });
Mocha的超时设定默认是2s,若是执行的测试超过2s的话,就会报timeout错误。
能够主动修改超时时间,有两种方法。
mocha -t 10000
describe('async', function () { this.timeout(10000); it('async', function (done) { lib.async(function (result) { done(); }); }); });
这样的话async
执行时间不超过10s,就不会报错timeout错误了。
异常应该怎么测试,如今有getContent
方法,他会读取指定文件的内容,可是不必定会成功,会抛出异常。
exports.getContent = function (filename, callback) { fs.readFile(filename, 'utf-8', callback); };
这时候就应该模拟(mock)错误环境了
describe("getContent", function () { var _readFile; before(function () { _readFile = fs.readFile; fs.readFile = function (filename, encoding, callback) { process.nextTick(function () { callback(new Error("mock readFile error")); }); }; }); // it(); after(function () { // 用完以后记得还原。不然影响其余case fs.readFile = _readFile; }) });
Mock小模块:muk
,略微优美的写法:
var fs = require('fs'); var muk = require('muk'); before(function () { muk(fs, 'readFile', function(path, encoding, callback) { process.nextTick(function () { callback(new Error("mock readFile error")); }); }); }); // it(); after(function () { muk.restore(); });
针对一些内部的方法,没有经过exports暴露出来,怎么测试它?
function _adding(num1, num2) { return num1 + num2; }
模块:rewire
it('limit should return success', function () { var lib = rewire('../lib/index.js'); var litmit = lib.__get__('limit'); litmit(10); });
在开发Web项目的时候,要测试某一个API,如:/user
,到底怎么编写测试用例呢?
使用:supertest
var express = require("express"); var request = require("supertest"); var app = express(); // 定义路由 app.get('/user', function(req, res){ res.send(200, { name: 'jerryc' }); }); describe('GET /user', function(){ it('respond with json', function(done){ request(app) .get('/user') .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(200) .end(function (err, res) { if (err){ done(err); } res.body.name.should.be.eql('jerryc'); done(); }) }); });
测试的时候,咱们经常关心,是否全部代码都测试到了。
这个指标就叫作"代码覆盖率"(code coverage)。它有四个测量维度。
行覆盖率(line coverage):是否每一行都执行了?
函数覆盖率(function coverage):是否每一个函数都调用了?
分支覆盖率(branch coverage):是否每一个if代码块都执行了?
语句覆盖率(statement coverage):是否每一个语句都执行了?
Istanbul 是 JavaScript 程序的代码覆盖率工具。
$ npm install -g istanbul
在编写过以上的测试用例以后,执行命令:
istanbul cover _mocha
就能获得覆盖率:
JerryC% istanbul cover _mocha module limit ✓ limit should success async ✓ async getContent ✓ getContent add ✓ add should #Promise ✓ should.reject ✓ should fulfilled 6 passing (32ms) ================== Coverage summary ====================== Statements : 100% ( 10/10 ) Branches : 100% ( 2/2 ) Functions : 100% ( 5/5 ) Lines : 100% ( 10/10 ) ==========================================================
这条命令同时还生成了一个 coverage 子目录,其中的 coverage.json 文件包含覆盖率的原始数据,coverage/lcov-report 是能够在浏览器打开的覆盖率报告,其中有详细信息,到底哪些代码没有覆盖到。
上面命令中,istanbul cover
命令后面跟的是 _mocha
命令,前面的下划线是不能省略的。
由于,mocha 和 _mocha 是两个不一样的命令,前者会新建一个进程执行测试,然后者是在当前进程(即 istanbul 所在的进程)执行测试,只有这样, istanbul 才会捕捉到覆盖率数据。其余测试框架也是如此,必须在同一个进程执行测试。
若是要向 mocha 传入参数,能够写成下面的样子。
$ istanbul cover _mocha -- tests/test.sqrt.js -R spec
上面命令中,两根连词线后面的部分,都会被看成参数传入 Mocha 。若是不加那两根连词线,它们就会被看成 istanbul 的参数(参考连接1,2)。
TESTS = test/*.test.js REPORTER = spec TIMEOUT = 10000 JSCOVERAGE = ./node_modules/jscover/bin/jscover test: @NODE_ENV=test ./node_modules/mocha/bin/mocha -R $(REPORTER) -t $(TIMEOUT) $(TESTS) test-cov: lib-cov @LIB_COV=1 $(MAKE) test REPORTER=dot @LIB_COV=1 $(MAKE) test REPORTER=html-cov > coverage.html lib-cov: @rm -rf ./lib-cov @$(JSCOVERAGE) lib lib-cov .PHONY: test test-cov lib-cov make test make test-cov
用项目自身的jscover和mocha,避免版本冲突和混乱
绑定Github账号
在Github仓库的Admin打开Services hook
打开Travis
每次push将会hook触发执行npm test
命令
注意:Travis会将未描述的项目看成Ruby项目。因此须要在根目录下加入.travis.yml
文件。内容以下:
language: node_js node_js: - "0.12"
Travis-cli还会对项目颁发标签,
or
若是项目经过全部测试,就会build-passing,
若是项目没有经过全部测试,就会build-failing
实施单元测试的时候, 若是没有一份通过实践证实的详细规范, 很难掌握测试的 "度", 范围过小施展不开, 太大又侵犯 "别人的" 地盘. 上帝的归上帝, 凯撒的归凯撒, 给单元测试念念紧箍咒不见得是件坏事, 反而更有利于发挥单元测试的威力, 为代码重构和提升代码质量提供动力.
这份文档来自 Geotechnical, 是一份很是可贵的经验准则. 你彻底能够以这份准则做为模板, 结合所在团队的经验, 整理出一分内部单元测试准则.
最后,介绍一个库:faker
他是一个能伪造用户数据的库,包括用户常包含的属性:我的信息、头像、地址等等。
是一个开发初期,模拟用户数据的绝佳好库。
支持Node.js和浏览器端。
测试框架 mocha
断言库:should.js、expect.js、chai
覆盖率:istanbul、jscover、blanket
Mock库:muk
测试私有方法:rewire
Web测试:supertest
持续集成:Travis-cli
http://stackoverflow.com/questions/153234/how-deep-are-your-unit-tests
http://www.codedata.com.tw/java/unit-test-the-way-changes-my-programming
https://github.com/visionmedia/superagent/blob/master/Makefile
若是本文对您有用
请不要吝啬大家的Follow与Start
这会大大支持咱们继续创做
「Github」
MZMonster :@MZMonster
JC_Huang :@JerryC8080