mocha异步测试源码分析
mocha异步测试源码分析
使用简介
mocha是一款功能丰富的javascript单元测试框架,它既可以运行在nodejs环境中,也可以运行在浏览器环境中。 javascript是一门单线程语言,最显著的特点就是有很多异步执行。同步代码的测试比较简单,直接判断函数的返回值是否符合预期就行了,而异步的函数,就需要测试框架支持回调、promise或其他的方式来判断测试结果的正确性了。mocha可以良好的支持javascript异步的单元测试。mocha会串行地执行我们编写的测试用例,可以在将未捕获异常指向对应用例的同时,保证输出灵活准确的测试结果报告。
同步测试
var assert = require('chai').assert;
describe('Array', function() { //Suite
describe('#indexOf()', function() { //Suite could be hierarchically nested
it('should return -1 when the value is not present', function() { // Test function
assert.equal(-1, [1,2,3].indexOf(5));
assert.equal(-1, [1,2,3].indexOf(0));
});
});
});
describe函数的第一个参数会被输出在控制台中,作为一个用例集的描述,而且这个描述是可以根据自己的需求来嵌套输出的,称之为:用例集定义函数。
it函数第一个参数用来输出一个用例的描述,第二个参数是一个函数,用来编写用例内容,用断言模块来判断结果的正确性,称之为用例函数。
异步测试
只需要在用例函数里边加一个done回调,异步代码执行完毕后调用一下done,就可以通知mocha,去执行下一个用例函数吧,就像下面这样:
describe('User', function() {
describe('#save()', function() {
it('should save without error', function(done) {
var user = new User('Luna');
user.save(function(err) {
if (err) throw err;
done();
});
});
});
});
数据结构
源码分析部分
mocha run entrypoint
Mocha.prototype.run = function (fn) {
...
return runner.run(done);
}
Runner.prototype.run = function (fn) {
function start () {
...
self.runSuite(rootSuite, function () {
debug('finished running');
self.emit('end');
});
}
...
start()
}
run test suite one by one
Runner.prototype.runSuite = function (suite, fn) {
...
function next (errSuite) {
...
var curr = suite.suites[i++];
if (!curr) {
return done();
}
self.runSuite(curr, next);
}
function done (errSuite) {
...
self.hook('afterAll', function () { //'afterAll' hook
self.emit('suite end', suite);
fn(errSuite);
});
}
this.hook('beforeAll', function (err) { //'beforeAll' hook
if (err) {
return done();
}
self.runTests(suite, next);
});
}
run test case one by one
Runner.prototype.runTests = function (suite, fn) {
function next (err, errSuite) {
// next test
test = tests.shift();
// all done
if (!test) {
return fn();
}
...
self.hookDown('beforeEach', function (err, errSuite) {//'beforeEach' hook
self.runTest(function (err) {
...
self.hookUp('afterEach', next);//'afterEach' hook
}
}
}
next();
}
run each test case with async support
Runner.prototype.runTest = function (suite, fn) {
var test = this.test;
test.run(fn);
}
Runnable.prototype.run = function (fn) {
if (this.async) {
this.resetTimeout();
try {
callFnAsync(this.fn);//this.fn is Runable itself and not the callback fn
} catch (err) {
emitted = true;
done(utils.getError(err));
}
return;
}
function done (err) {
var ms = self.timeout();
if (self.timedOut) {
return;
}
self.clearTimeout();
self.duration = new Date() - start;
finished = true;
if (!err && self.duration > ms && self._enableTimeouts) {
err = new Error('Timeout of ' + ms +
'ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.');
}
fn(err);
}
function callFnAsync (fn) {
var result = fn.call(ctx, function (err) {//this is the done function passed to async test code
...
done();
});
}
}