( 三)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源码阅读先写到这吧。。我本身表达很差,若是真的有人愿意看留个言吧

相关文章
相关标签/搜索