( 三)Mocha源碼閱讀: 測試執行流程一執行用例

(一)Mocha源碼閱讀: 項目結構及命令行啓動bash

(二)Mocha源碼閱讀: 測試執行流程一之引入用例異步

(三)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

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

相關文章
相關標籤/搜索