这是原贴地址: https://cnodejs.org/topic/586e1810df04f6ab76081dc5
最近在用eggjs写后台,也想发布api之类的文档。以前看到过通过单元测试改造生成API文档,自己还是比较认可这个理念的,于是将其拿过来,改造了一下,适用于eggjs。
首先看看现在单测的写法:
test\controller\my.test.js
'use strict';
const test = require('../../../app/common/test');
describe('test/app/controller/home.test.js', () => {
before(test.before);
afterEach(test.afterEach);
it('should GET doc', () => {
test({
file: 'user',
group: '用户相关API',
title: '获取用户信息',
method: 'get',
url: '/user/:id',
})
.get('/', {
id: { value: '123abc', type: 'String', required: true, desc: '' },
})
.expect('hi, egg')
.expect(200)
});
});
单测代码书写方式没有太大变化,只是为了方便,做了一点点包装。
app\common\test.js
'use strict';
const mm = require('egg-mock');
const assert = require('assert');
let app;
let dir;
if(!global.docTimeout) {
after( ()=>{
fs.writeFileSync(path.resolve(dir, './docs/index.js'), JSON.stringify(global.docs, null, 4));
mdRender(global.docs)
console.log('文档生成完毕')
} )
global.docTimeout = true
}
module.exports = (opt) => {
class Request {
constructor(opt) {
this.req = app.httpRequest();
this.opt = opt;
}
request(type, url, params) {
return this.req[type](url).send(params).expect(200, (err, res) => {
if(!global.docs) global.docs = []
this.opt.params = params
this.opt.res = res.text
global.docs.push(this.opt)
});
}
get(url, params) {
return this.request('get', url, params);
}
post(url, params) {
return this.request('post', url, params);
}
}
return new Request(opt)
};
module.exports.before = () => {
app = mm.app();
//记录根目录
if(!dir){
dir = app.config.baseDir
}
return app.ready();
};
module.exports.afterEach = mm.restore;
// markdown 渲染
const fs = require('fs');
const path = require('path');
function mdRender(docs){
const mdStr = {};
docs.forEach((obj) => {
if (!mdStr[obj.group]) {
mdStr[obj.group] = '';
mdStr[obj.group] += '## ' + obj.group + '\n\n';
}
const fields = {};
mdStr[obj.group] += `### ${ obj.title } \`${ obj.method }\` ${ obj.url } \n\n#### 参数\n`;
mdStr[obj.group] += '\n参数名 | 类型 | 是否必填 | 说明\n-----|-----|-----|-----\n';
Object.keys(obj.params).forEach(function (param) {
const paramVal = obj.params[param];
fields[param] = paramVal['value'];
mdStr[obj.group] += `${ param } | ${ paramVal['type'] } | ${ paramVal['required'] ? '是' : '否' } | ${ paramVal['desc'] } \n`;
});
mdStr[obj.group] += '\n#### 使用示例\n\n请求参数: \n\n';
mdStr[obj.group] += '```json\n' + JSON.stringify(fields, null, 2) + '\n```\n';
mdStr[obj.group] += '\n返回结果:\n\n';
if (obj.url.indexOf(':') > -1) {
obj.url = obj.url.replace(/:\w*/g, function (word) {
return fields[word.substr(1)];
});
}
mdStr[obj.group] += '```json\n' + JSON.stringify(obj.res, null, 2) + '\n```\n';
mdStr[obj.group] += '\n';
fs.writeFileSync(path.resolve(dir, './docs/', obj.file + '.md'), mdStr[obj.group]);
})
}
代码编写仓促,难免有疏漏之处。
起初另外由于不知道怎么在所有单测执行完毕后触发事件,开始尝试在process上监听exit事件,输出最后的文档。后来去eggjs提了一个issue,才知道可以直接用after。
感觉eggjs的团队,问题回答总是这样高效、快速。
…
先赞一个,然后请教下「在测试里面写文档」的原因?
为什么 API 的注释要写在单测里面,为什么不直接写到 controller 或 service?如果单测里面对同个 API 测试多次呢?
另外,mocha 应该是有对应的 event 的,你可以看下源码或文档,https://github.com/mochajs/mocha/issues/1860 https://stackoverflow.com/questions/18660916/how-can-i-subscribe-to-mocha-suite-events
@atian25 感谢回复,解释下我自己这样做的原因。
-
service层方法加注释,还是比较清晰的,因为参数的定义直接就在方法中。service层,我也会添加注释。
-
controller层参数,通常从query或body中获取,而内部获取参数往往是用到的时候再获取,一般注释也会添加,但比较分散,而且还不知道怎么导出。
-
单测需要生成的文档,是对外发布的api接口,只是针对controller层,可能文中没有表达清楚。之所以选择在单测中去做,因为修改方法后,紧接着就是修改单测代码。这样可以拿到最新的测试请求数据,以及业务调整后请求的返回值。而对于项目来说,单测是一定要写的,所以结合起来,感觉还比较好。之前接触express和koa时,感觉 通过单测来写api文档 这个思路,与我最终想要的很一致,其他的文档库,总觉得多少有点别扭。
-
一个接口多个单测的情况下,现在的想法是,不需要api注释的单测,可能就直接test().get(’/’, { id: 3 ).expect(200) ,后面会逐步来考虑。
got,这类感觉还是要通过「定义即文档」的方式,在 swagger 或 grpc 等地方定义,然后自动生成测试代码和文档等。
我这边正在实践 GRPC
嗯,期待下。后面持续关注。