博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
( 三)Mocha源码阅读: 测试执行流程一执行用例
阅读量:6256 次
发布时间:2019-06-22

本文共 6262 字,大约阅读时间需要 20 分钟。

正文

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

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

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

你可能感兴趣的文章
为什么要评审代码?
查看>>
小程序开发前的准备工作之【深入封装Component】
查看>>
AFN3.0源码解析
查看>>
oracle的drop命令
查看>>
设计与梳理企业二级流程的路线方法
查看>>
Python正则表达式指南
查看>>
使用css3制作渐变分割线
查看>>
垃圾回收概念与算法
查看>>
IconFont 图标svg
查看>>
TFS实现需求工作项自动级联保存
查看>>
springmvc 4.x 处理json 数据时中文乱码
查看>>
nginx 重启命令
查看>>
一花一世界 一叶一菩提
查看>>
Python练习(day7)
查看>>
网络工程师笔试题总结
查看>>
我的友情链接
查看>>
C# DataTable的詳細用法
查看>>
vSphere网络原理及vSwitch
查看>>
df 命令
查看>>
jQuery 简介
查看>>