( 三)Mocha源码阅读: 测试执行流程一执行用例

(一)Mocha源码阅读: 项目结构及命令行启动

(二)Mocha源码阅读: 测试执行流程一之引入用例

正文

用例执行算是此次源码阅读的核心,接上篇引入用例

// 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的类,这个就是控制执行流程的类,我们先往下继续看

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源码阅读先写到这吧。。我自己表达不好,如果真的有人愿意看留个言吧

转载于:https://juejin.im/post/5ce4eee3f265da1b70047f7a

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值