方法
以下示例均基于mocha.
异步
在异步操作完成时,调用回调(通常是 done ) 即可。
describe('User', function() { describe('#save()', function() { it('should save without error', function(done) { var user = new User('Luna'); user.save(done); }); }); }); |
Mocha的超时设定默认是2s,如果执行的测试超过2s的话,就会报timeout错误。可以修改超时时间,有两种方法:
命令行式
mocha -t 10000
API式
describe('async', function () { this.timeout(10000); it('async', function (done) { lib.async(function (result) { done(); }); }); }); |
Promises
如果异步操作的返回结果是 Promise ,可以使用 Chai as Promised 。
'use strict'; require('should'); require('chai').use(require('chai-as-promised')); describe('test/app/simple/promise.test.js', () => { it('#Promise', ()=> { (new Promise(function(resolve) { resolve(10); })).should.be.a.Promise(); (10).should.not.be.a.Promise(); }); it('#fulfilled', () => { return new Promise(resolve => resolve(10)) .should.be.fulfilled(); }); it('#fulfilledWith', () => { return new Promise((resolve) => resolve(10)) .should.be.fulfilledWith(10); }); it('#rejected', () => { return new Promise((resolve, reject) => reject(new Error('boom'))) .should.be.rejected(); }); }); |
私有方法
一般测试只测试接口或公共方法,并不直接测试私有方法。但是如果当私有方法特别多且复杂时,测试私有方法比测试接口或公共方法更直接、有效。
严格上讲,javascript并没有私有方法的概念,这里所讨论的私有方法指未被导出( exports )的方法。
一个简单的盒子,在一个 cal.js 里有一个 add 方法,如下所示:
function add(a, b) {
return a + b;
}
此方法为私有方法,无法直接通过 cal 测试,但可以通过 rewire 获取。
rewire adds a special setter and getter to modules so you can modify their behaviour for better unit testing.
获取上面的私有方法,并测试:
it('1 + 1 should equals 2', function () {
let cal = rewire('./cal.js');
let add = cal.__get__('add');
add(1, 1).should.be.eql(2);
});
除了获取私有方法, rewire 还可以设置私有变量或方法, 以便测试。
比如有这样一个模块,
// lib/myModules.js // With rewire you can change all these variables var fs = require("fs"), path = "/somewhere/on/the/disk"; function readSomethingFromFileSystem(cb) { console.log("Reading from file system ..."); fs.readFile(path, "utf8", cb); } exports.readSomethingFromFileSystem = readSomethingFromFileSystem; |
设置并获取私有变量。
myModule.__set__("path", "/dev/null"); myModule.__get__("path"); // = '/dev/null' 还可以用作mock。 var fsMock = { readFile: function (path, encoding, cb) { expect(path).to.equal("/somewhere/on/the/disk"); cb(null, "Success!"); } }; myModule.__set__("fs", fsMock); |
mock
mock就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。nodejs中常用的mock工具有 muk 、 sinon 等,以sinon为例简要说明一下其用法。
需要测试的方法:
function once(fn) { var returnValue, called = false; return function () { if (!called) { called = true; returnValue = fn.apply(this, arguments); } return returnValue; }; } Spy it("calls the original function", function () { var spy = sinon.spy(); var proxy = once(spy); proxy(); assert(spy.called); }); Stubs it("returns the return value from the original function", function () { var stub = sinon.stub().returns(42); var proxy = once(stub); assert.equals(proxy(), 42); }); Mocks it("returns the return value from the original function", function () { var myAPI = { method: function () {} }; var mock = sinon.mock(myAPI); mock.expects("method").once().returns(42); var proxy = once(myAPI.method); assert.equals(proxy(), 42); mock.verify(); }); |
web
主要依赖 SuperTest ,已在工具部分说明,不再赘述。
更多
持续集成
持续集成(CI, Continuous integration)是一种软件开发实践,即团队开发成员经常集成它们的工作。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。
github 上大部分 ci 都是基于 Travis CI 。详细接入方案见官方文档,不在此讨论。
代码风格
当写js代码的时候,一个校验工具可以确保一个项目遵循代码规范、帮助我们避免愚蠢的错误。目前比较流行js校验工具有 JSLint 、 JSHint 、 JSCS 、 ESLint , 它们之前的差异比较可以参考 A Comparison of JavaScript Linting Tools ,推荐使用 ESLint 。
代码风格检查同样也可以集成到 ci,只需要在 ci 命令前追加检查命令即可。以 eslint 为例:
"scripts": {
// ..
"lint": "eslint .",
"cov": "istanbul cover .",
"ci": "tnpm run lint && TEST_TIMEOUT=60000 istanbul cover ."
},
此时构建会先做代码风格检查,再做单元测试、覆盖率统计,如代码风格检查失败,会直接导致构建中断。
写在后面的
原本计划写一个nodejs的测试参考,帮助还不太了解nodejs测试的同学快速入门,在写的过程中才发现,需要的东西太多,而自己又知道的太少。虽然在一个应用的测试中覆盖率达到了90%,但并不代码在测试方面可以拿到90分。
正如前面写到的,”剩下的10,还有很长的路“,甚至比从0到90更长,唯有不断的学习才能进一步提升,希望自己还能再写个下篇《Nodejs测试:从90到100》。