01月03, 2018

如何使用mocha等测试框架和supertest包对Thinkjs3进行业务逻辑测试

本文主要介绍了如何在ThinkJs3.0框架下使用ava,mocha等方法进行业务逻辑测试

引言

在软件开发的过程中,测试对于检验项目的可靠性非常重要。测试的方案有很多,比如手动测试,使用mocha,ava等框架的自动化测试。测试的重点也有不同,有侧重业务逻辑正确性的,有侧重性能的。在Thinkjs3框架下,虽然文档没提,但是官方提供了一种针对model进行的单元测试实践,具体方案在官方Github项目的issue #841中。这种方法需要先在model中写好测试函数,然后通过think.model实例化对象并调用测试函数完成测试。而对于controller层体现的业务逻辑,该方法并不适用。本文介绍一种方法,可以通过在mocha测试框架下模拟http请求的方式去测试业务逻辑,并可以用于travis这样的持续集成中进行自动化测试。

方法

首先在项目根目录下创建testing.js的测试环境文件,内容基本复制production.js:

const Application = require('thinkjs');
const path = require('path');

const instance = new Application({
  ROOT_PATH: __dirname,
  proxy: true, // use proxy
  env: 'testing'
});

instance.run();

instance.runInWorker({ port: 2333 }); #添加这两行
module.exports = instance;

然后在test文件夹下,创建一个测试文件:

const assert = require('assert');
const request = require('supertest');
const path = require('path');
const instance = require(path.join(process.cwd(), 'testing.js'));

describe('Test Title', function() {
  describe('Test subtitle', function() {
    it ('Test description', function(done){
      const f = function() {
        request(think.app.server).post([url])
         .set('Content-Type', 'application/json')
          .send({
          // ...构造请求参数,supertest具体用法参见supertest文档
          })
          .expect(200)
          .end(function(err, res) {
            if (err) throw err;
            // ...测试逻辑
            done();
          });
      };
      setTimeout(f, 4000);
    })
  })
  after(function () {
    process.exit();
  })
});

并添加adapter.testing.js,在其中声明用于测试环境的配置,比如测试数据库地址,写法同adapter.js,根据官方文档,adapter.js中的同名配置会被覆盖。 最后在package.json中加入test命令: linux环境:

"test": "THINK_UNIT_TEST=1 mocha -t 20000"

windows环境:

"test": " set THINK_UNIT_TEST=true && mocha -t 20000"

不要忘记安装mocha等相关依赖,在测试前要先npm run compile编译

原理解释

我们要知道为什么这样可以启动一个http server并用于测试服务。


THINK_UNIT_TEST=1如果我们基于原生的koa或者express,那么我们直接用koa()或者express()就能创建出一个http服务。但是按照#841的方法,是不会启动http服务的。原因在于这句环境变量设置。我们追踪testing.js中的instance.run()方法,会定位到thinkjs源码中的lib/application.js文件,会看见根据环境变量与process参数的不同,框架有四条启动分支:

run() {
    if (pm2.isClusterMode) {
      throw new Error('can not use pm2 cluster mode, please change exec_mode to fork');
    }
    // start file watcher
    if (cluster.isMaster) this.startWatcher();

    const instance = new ThinkLoader(this.options);
    const argv = this.parseArgv();
    try {
      console.error(argv);
      if (process.env.THINK_UNIT_TEST) {
        instance.loadAll('worker', true);
      } else if (argv.path) {
        instance.loadAll('worker', true);
        return this.runInCli(argv);
      } else if (cluster.isMaster) {
        instance.loadAll('master');
        return this.runInMaster(argv);
      } else {
        instance.loadAll('worker');
        return this.runInWorker(argv);
      }
    } catch (e) {
      console.error(e);
    }
  }

其中,使用了THINK_UNIT_TEST环境会执行try中的第一分支,第一分支如果进一步追踪会发现框架使用thinkLoader加载了所有的配置等,因此think.model可以正常的实例化模型。但是第一分支没有使用return 语句来真正的以某种模式启动服务。在其他三个分支里,框架使用runInClirunInMasterrunInWorker将服务启动了起来。所以我们需要手动将http服务run起来。

之所以不直接尝试进入其他分支,是因为进入条件不满足。第二第三分支的进入条件是process的argv参数里argv[2]必须存在且为path(第二分支,以命令行方式运行服务)或者port(第三分支)。在平常使用时(npm run start [port]),我们使用node [env].js [port]的方式启动服务,此时port会被正则匹配并解析到argv中,执行第三分支(解析过程具体可以看application.js中的parseArgv()函数)。第二分支我觉得应该是在crontab(定时任务)的场景下使用,被框架自己调用。

而在测试框架中执行时,process的argv[2]中会被注入测试框架自己的参数,比如我们的mocha中,argv[2]='-t',ava中此项会被注入一个json化的对象字符串,但是由于thinkjs没有对path的格式做校验,因此会进入第二分支,并产生错误。


instance.runInWorker({ port: 2333 })使用这句可以手动启动服务,在传入参数中指定port即可。之所以不runInMaster是因为会引发cluster.fork is not a function这个错误,而runInWorker可以直接启动一个工作进程,对于基本的业务逻辑测试已经足够了。这里我们就完成了启动一个http服务。


setTimeout(f, 4000);在测试代码中,我们使用延时任务以等待服务启动的时间。thinkJs提供了'appReady'事件,所以这里可以考虑用think.app.on('appReady')改写。这里如果不做相应的等待,server对象不会被注入到think.app.server中,也就无法使用supertest


process.exit()测试完成后,需要在mocha的after方法里手动关闭测试进程,原因是之前使用runInWorker方法启动的服务仍然在运行。这种方法虽然可以关闭process完成测试,但是Nodejs会残留。不过在travis持续集成下的自动化测试中,最终系统资源都会释放掉。


mocha -t 20000由于mocha默认一个测试应该在两秒内完成,所以需要用-t参数延时以免超时导致测试不通过。

总结

缺点

不能干净的结束掉测试进程,对于本地测试会需要手动关闭node进程

可能的改进思路

在config中,声明一个createServer函数,来自定义启动,以获取supertest所需的server对象。

感谢

@wurining @welefen

本文链接:https://blog.magichc7.com/post/thinkjs3-functiontest.html

-- EOF --

相关评论