在Node.js中經過單元測試爲API自動生成文檔

在開發中,爲項目生成文檔是很常見的需求,不少第三方庫(如jsdocswagger等)的作法是爲須要生成文檔的函數編寫相應的符合規範的註釋,而後運行相應的命令,生成一個靜態網頁形式的文檔。javascript

用註釋生成文檔的好處是能夠爲不管是普通函數仍是 API,只要編寫了相應的註釋都能生成相應的文檔,然而這種作法總以爲有點繁瑣,尤爲是隻須要爲 API 生成文檔的時候,須要手動編寫大量的輸入和輸出做爲使用示例。並且我只想須要 markdown 形式的文檔,丟在內部 Gitlab 的 wiki 上供前端人員査閱,而後能夠根據 commit 的 history 查閱不一樣的版本。前端

不想手動爲 API 文檔編寫大量的輸入輸出,那哪裏會有輸入輸出呢,就很容易的想到了單元測試會產生輸入和輸出。好,那就用單元測試來爲 API 生成文檔。java

我在單元測試中主要用的庫有 mochasupertestpower-assert,Web 框架爲 expressnode

完整代碼示例:express

// app.js
const express = require('express');
const bodyParser = require('body-parser');
const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.post('/user/create', function (req, res) {
  const name = req.body.name;
  res.json({
    status: 200,
    error_code: 0,
    message: 'success',
    data: {
      id: '123abc',
      name: 'node',
      gender: 'male',
      age: 23,
    },
  });
});

app.get('/user/search', function (req, res) {
  const name = req.query.name;
  res.json({
    status: 200,
    error_code: 0,
    message: 'success',
    data: {
      id: '123abc',
      name: 'node',
      gender: 'male',
      age: 23,
    },
  });
});

app.get('/user/:id', function (req, res) {
  const userId = req.params.id;
  res.json({
    status: 200,
    error_code: 0,
    message: 'success',
    data: {
      id: '123abc',
      name: 'node',
      gender: 'male',
      age: 23,
    },
  });
});

app.listen(3000, function () {
  console.log(`Server is listening on 3000`);
});
module.exports = app;

下面是測試文件。npm

// test/uset.test.js
const assert = require('power-assert');
const supertest = require('supertest');
const U = require('../utils');
const app = require('../app');
const agent = supertest.agent(app);

describe('Test', function () {
  it('should create user success', function (done) {
    U.test({
      agent,
      file: 'user',
      group: '用戶相關API',
      title: '建立用戶',
      method: 'post',
      url: '/user/create'
      params: {
        name: { value: 'node', type: 'String', required: true, desc: '名稱' },
        gender: { value: 'male', type: 'String', required: false, desc: '性別' },
        age: { value: 23, type: 'Int', required: false, desc: '' },
      },
      headers: { entrance: 'client' },
      expect: 200,
      callback (err, res) {
        if (err) return done(err);

        assert(res.body.data.name === 'node');
        assert(res.body.data.age === 23);
        done();
      },
    });
  });

  it('should search user success', function (done) {
    U.test({
      agent,
      file: 'user',
      group: '用戶相關API',
      title: '搜索用戶',
      method: 'get',
      url: '/user/search'
      params: {
        name: { value: 'node', type: 'String', required: true, desc: '名稱' },
      },
      expect: 200,
      callback (err, res) {
        if (err) return done(err);

        assert(res.body.data.name === 'node');
        assert(res.body.data.age === 23);
        done();
      },
    });
  });

  it('should search user success', function (done) {
    U.test({
      agent,
      file: 'user',
      group: '用戶相關API',
      title: '獲取用戶信息',
      method: 'get',
      url: '/user/:id'
      params: {
        id: { value: '123abc', type: 'String', required: true, desc: '' },
      },
      expect: 200,
      callback (err, res) {
        if (err) return done(err);

        assert(res.body.data.name === 'node');
        assert(res.body.data.age === 23);
        done();
      },
    });
  });
});

在測試文件中,測試用例的代碼調用到了 utils.js 中的 test 方法,該方法的主要做用是接收單元測試的輸入和輸出並生成相應的文檔,其中須要向 test 方法傳入一個對象做爲參數,對象中的字段解讀以下:json

agent:調用 API 的代理。
file:生成的文檔的文件名稱。
group:某一組文檔的名稱。
title:接口的名稱。
method:接口的方法。
params:接口的參數,即輸入。
headers: 添加到請求頭中的信息。
expect:supertest 的expect。
callback:supertest 方法的 end 的回調函數。markdown

utils.js代碼以下:app

// utils.js
const path = require('path');
const fs = require('fs');

const mdStr = {};
exports.test = function (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)];
    });
  }

  obj.agent[obj.method](obj.url)
  .set(obj.header || {})
  .query(fields)
  .send(fields)
  .expect(obj.expect)
  .end(function (err, res) {
    mdStr[obj.group] += '```json\n' + JSON.stringify(res.body, null, 2) + '\n```\n';
    mdStr[obj.group] += '\n';

    if (process.env['GEN_DOC'] > 0) {
      fs.writeFileSync(path.resolve(__dirname, './docs/', obj.file + '.md'), mdStr[obj.group]);
    }
    obj.callback(err, res);
  });
}

這樣,在根目錄建立一個 docs 目錄,運行 npm run test:doc 命令,就會在 docs 目錄下生成文檔。若是運行單元測試不想生成文檔,直接用npm test就能夠了,相應的package.json配置以下:框架

"scripts": {
  "test": "export NODE_ENV='test' && mocha",
  "test:doc": "export NODE_ENV='test' && export GEN_DOC=1 && mocha"
}

若是不想爲某個 API 生成文檔,就不要調用 utils 的 test,直接按原生的寫法就能夠了。

若須要對參數進行簽名,可在調用 test 方法時,增長形如sign: true的配置,而後在 test 方法中作相應的判斷和實現相應的簽名。

生成的文檔內容形式以下:
test_doc

原文地址:經過單元測試爲API自動生成文檔

相關文章
相關標籤/搜索