正文
用例执行算是此次源码阅读的核心,接上篇引入用例
// lib/mocha.jsMocha.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的类,这个就是控制执行流程的类,我们先往下继续看
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。
Runner
Runner类是一个调度者,掌控着所有用例同步/异步运行,钩子运行,报错处理,超时处理等。
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函数,预示着将有很多递归调用。
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的回调
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源码阅读先写到这吧。。我自己表达不好,如果真的有人愿意看留个言吧