(三)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源碼閱讀先寫到這吧。。我本身表達很差,若是真的有人願意看留個言吧