测试驱动

2018-11-24 03:51:04

强烈建议基于测试驱动开发。测试驱动开发,可以有效沉淀开发成果,当代码出现变更时也能尽快锁定潜在问题,从而显著提升代码的鲁棒性

EggBorn在EggJS的基础上,提供了一个便捷的框架,可以很方便的实现基于测试驱动的开发

下面是模块test-todo测试驱动的示范。可以此为范例,不断扩充测试用例,尽可能多的提高代码覆盖率

  1. 用户登录
  2. 新建todo
  3. 提交todo(从草稿状态提交到正常状态
  4. 查询todo
  5. 删除todo

测试用例

src/module/test-todo/backend/test/controller/test.test.js

const { app, mockUrl, mockInfo, assert } = require('egg-born-mock')(__dirname);

describe('[your tests start from here]', () => {
  it('[atom]', async () => {
    app.mockSession({});

    // atomClass info
    const atomClassModule = mockInfo().relativeName;
    const atomClassName = 'todo';

    // login as root
    await app.httpRequest().post(mockUrl('/a/authsimple/passport/a-authsimple/authsimple')).send({
      auth: 'root',
      password: '123456',
    });

    // create
    let result = await app.httpRequest().post(mockUrl('/a/base/atom/create')).send({
      atomClass: { module: atomClassModule, atomClassName, atomClassIdParent: 0 },
    });
    assert(result.body.code === 0);
    const atomKey = result.body.data;

    // submit
    result = await app.httpRequest().post(mockUrl('/a/base/atom/submit')).send({
      key: atomKey,
      item: {
        atomName: 'test',
        description: 'this is a test',
      },
    });
    assert(result.body.code === 0);

    // read
    result = await app.httpRequest().post(mockUrl('/a/base/atom/read')).send({
      key: atomKey,
    });
    assert(result.body.code === 0);

    // delete
    result = await app.httpRequest().post(mockUrl('/a/base/atom/delete')).send({
      key: atomKey,
    });
    assert(result.body.code === 0);

  });
});
名称 说明
mockUrl 用于构造完整的后端API接口地址
mockInfo 用于获取当前测试所属模块的基本信息。比如当前模块名是test-todo,但为了提升代码的灵活性,可以通过mockInfo().relativeName获取
app.mockSession 用于模拟Session环境

执行测试

$ npm run test:backend

测试覆盖率统计

$ npm run cov:backend

后端API路由version/test

测试用例执行之前,每个模块的后端API路由version/test都会自动执行

可以在这个阶段准备一些测试数据(如测试用的角色用户权限等等)

以模块test-todo为例,添加如下测试数据:

  1. 添加用户demo001
  2. 添加用户的登录认证信息
  3. 把用户demo001添加到角色registered

test-todo/backend/src/service/version.js

async test() {
  // create test users: demo001
  const users = [
    { userName: 'demo001', roleName: 'registered' },
  ];
  const userIds = [];
  for (const user of users) {
    // add user
    userIds[user.userName] = await this.ctx.meta.user.add({
      userName: user.userName,
      realName: user.userName,
    });
    // add auth info for login
    await this.ctx.performAction({
      method: 'post',
      url: '/a/authsimple/auth/add',
      body: {
        userId: userIds[user.userName],
        password: '123456',
      },
    });
    // role
    const role = await this.ctx.meta.role.get({ roleName: user.roleName });
    // add user role
    await this.ctx.meta.role.addUserRole({
      userId: userIds[user.userName],
      roleId: role.id,
    });
  }
}

app.meta.isTest, app.meta.isLocal

可以通过app.meta.isTestapp.meta.isLocal,在合适的环境加载合适的代码逻辑

比如,模块test-cook里面的资源,只允许在测试环境开发环境中生效,在生产环境无效,可以操作如下:

test-cook/backend/src/models.js

const cook = require('./model/cook.js');
const cookType = require('./model/cookType.js');
const cookPublic = require('./model/cookPublic.js');

module.exports = app => {
  const models = {
  };
  if (app.meta.isTest || app.meta.isLocal) {
    Object.assign(models, {
      cook,
      cookType,
      cookPublic,
    });
  }
  return models;
};

模块test-cook

模块test-cook是开箱即用的测试模块,提供大量的测试驱动开发指引



评论: