(三)Mocha源码阅读: 测试执行流程一执行用例async
用例执行算是这次源码阅读的核心,接上篇引入用例函数
// lib/mocha.js
Mocha.prototype.run = function(fn) {
if (this.files.length) {
this.loadFiles();
}
var suite = this.suite;
var options = this.options;
options.files = this.files;
var runner = new exports.Runner(suite, options.delay);
...
复制代码
loadFiles结束后this.suite就是收集好的全部用例, 咱们看到suite传入了个叫Runner的类,这个就是控制执行流程的类,咱们先往下继续看post
Mocha.prototype.run = function(fn) {
...
var runner = new exports.Runner(suite, options.delay);
// reporter做用是生成最后的测试报告
var reporter = new this._reporter(runner, options);
runner.ignoreLeaks = options.ignoreLeaks !== false;
runner.fullStackTrace = options.fullStackTrace;
runner.asyncOnly = options.asyncOnly;
runner.allowUncaught = options.allowUncaught;
...
复制代码
runner实例传到了reporter里面,reporter和runner交互是经过发布订阅模式, reporter会监听runner运行时发出的用例成功/失败,终止和完成的消息来作相应数据展现,reporter后面单独一篇再讲。 再往下运行能够看到是把options赋给了runner实例上的属性。测试
Mocha.prototype.run = function(fn) {
...
function done(failures) {
if (reporter.done) {
reporter.done(failures, fn);
} else {
fn && fn(failures);
}
}
return runner.run(done);
};
复制代码
最后就是runner.run开始运行,done做为回调结束后通知reporter。ui
Runner类是一个调度者,掌控着全部用例同步/异步运行,钩子运行,报错处理,超时处理等。this
function Runner(suite, delay) {
...
//根suite
this.suite = suite;
this.started = false;
//获取全部test的数量
this.total = suite.total();
this.failures = 0;
//Runner也是继承了EventEmitter类,这两个监听事件是test和hook结束后就会检查一下是否有global上的内存泄漏
this.on('test end', function(test) {
self.checkGlobals(test);
});
this.on('hook end', function(hook) {
self.checkGlobals(hook);
});
...
//找到global上的全部变量,而后存下来在上面test end/hook end事件监听中对比来判断是否有泄漏。
this.globals(this.globalProps().concat(extraGlobals()));
}
复制代码
Runner.prototype.checkGlobals = function(test) {
if (this.ignoreLeaks) {
return;
}
var ok = this._globals;
var globals = this.globalProps();
var leaks;
if (test) {
ok = ok.concat(test._allowedGlobals || []);
}
if (this.prevGlobalsLength === globals.length) {
return;
}
this.prevGlobalsLength = globals.length;
leaks = filterLeaks(ok, globals);
this._globals = this._globals.concat(leaks);
if (leaks.length > 1) {
this.fail(
test,
new Error('global leaks detected: ' + leaks.join(', ') + '')
);
} else if (leaks.length) {
this.fail(test, new Error('global leak detected: ' + leaks[0]));
}
};
复制代码
主流程最后调用了Runner的run方法
Runner.prototype.run = function(fn) {
...
function start() {
...
self.started = true;
...
// 核心就是调用runSuite
self.runSuite(rootSuite, function() {
...
self.emit('end');
});
}
...
if (this._delay) {
// for reporters, I guess.
// might be nice to debounce some dots while we wait.
this.emit('waiting', rootSuite);
rootSuite.once('run', start);
} else {
start();
}
}
复制代码
从runSuite开始后面到函数是对suite这个树结构进行了一个遍历, runTest, runHook包括runSuite在内的函数内部定义了不少next函数,预示着将有不少递归调用。spa
Runner.prototype.runSuite = function(suite, fn) {
...
this.emit('suite', (this.suite = suite));
...
// next和done咱们一会再看
function next(errSuite) {
...
}
function done(errSuite) {
...
}
this.nextSuite = next;
// 看到最后调用了this.hook,执行完beforeAll钩子函数后,进入到runTests里,传了两个参数很关键。
this.hook('beforeAll', function(err) {
if (err) {
return done();
}
/**
* suite这里是runSuite的参数,第一次是根suite, 而next会在内部调用runSuite并传入下一个suite,
* 也就是咱们要理解传给runTests的suite只是当前遍历到的suite, 而next理解为回调便可,
* 他只是这个suite跑完全部test后要执行下一个suite的回调。
*/
self.runTests(suite, next);
});
}
复制代码
Runner.prototype.runTests = function(suite, fn) {
...
//runTests看着和runSuite很相似, 开头先把suite.tests复制了一份
var tests = suite.tests.slice();
...
function next(err, errSuite){
...
// next test
// 调用next把第一个test用例拿出来
test = tests.shift();
// all done
// 若是没有test了,那说明当前suite的test已经所有运行结束,调用fn也就是runSuite中的next调用下一个suite
if (!test) {
return fn();
}
...
// execute test and hook(s)
// emit('test')实际上是为了测试报告, hoodDown调用钩子函数
self.emit('test', (self.test = test));
self.hookDown('beforeEach', function(err, errSuite) {
...
...
//lots of code..
...
// 调用runTest流程的下一步
self.runTest(function(err) {
if (err) {
...
self.fail(test, err);
...
}
...
self.emit('test end', test);
self.hookUp('afterEach', next);
});
});
}
next();
}
复制代码
Runner.prototype.runTest = function(fn) {
...
// 这里能够推测出test里面若运行报错会emit error消息
test.on('error', function(err) {
self.fail(test, err);
});
// allowUncaught应该是容许不捕获非咱们写的用例的报错。
if (this.allowUncaught) {
test.allowUncaught = true;
return test.run(fn);
}
try {
// 运行用例了。我我的倾向于先不看test.run, 咱们如今能够知道test run完确定是调fn, 也就是runTests中的回调
test.run(fn);
} catch (err) {
fn(err);
}
};
复制代码
runTests调用完runTest的回调prototype
self.runTest(function(err) {
if (err) {
...
// 记录一下,给reporter发个消息
self.fail(test, err);
...
}
...
self.emit('test end', test);
/**
* 这个next咱们回到runTests看的话其实就是自身,只不过这一次是tests.shift获得下一个test,
* 若是当前suite的test都跑完,咱们就回到runSuite的next函数里了
*/
self.hookUp('afterEach', next);
});
});
复制代码
到suite的next函数这里,当前suite的tests其实已经所有跑完了
function next(errSuite) {
if (errSuite) {
...
return done(errSuite);
}
/**
* 这里要开始遍历子suite了,curr是第一个子suite, 子suite做为参数调用runSuite,
* 自此造成了纵向的递归suite,咱们也就能够嵌套随便几层的suite
*/
var curr = suite.suites[i++];
if (!curr) {
return done();
}
...
self.runSuite(curr, next);
...
}
复制代码
若是suite的子suite遍历完会调用done,代码也很简单
function done(errSuite) {
if (afterAllHookCalled) {
fn(errSuite);
} else {
// mark that the afterAll block has been called once
// and so can be skipped if there is an error in it.
afterAllHookCalled = true;
// remove reference to test
delete self.test;
self.hook('afterAll', function() {
self.emit('suite end', suite);
fn(errSuite);
});
}
}
复制代码
注意fn是runSuite的参数,也就是可能对应next中self.runSuite(curr, next)的next来进行下一个suite。若是是根suite,就到了最前面的run方法
Runner.prototype.run = function(fn) {
...
self.runSuite(rootSuite, function() {
...
self.emit('end');
});
...
}
复制代码
至此整个流程也就结束了, emit end告诉reporter能够来个总结了。
后面回到test.run看下咱们的用例是怎么被调用的。
Mocha源码阅读先写到这吧。。我本身表达很差,若是真的有人愿意看留个言吧