浅谈mock

阅读以前

但愿你能有如下基础,方便阅读:html

  • ECMAScript 6 (ES6)

为何须要Mock

image
这样的场景,相信你们会以为似曾相识。

现今的业务系统已经不多是孤立存在的了,尤为对于一个大公司而言,各个部门之间的配合很是密切,咱们或多或少都须要使用兄弟团队或是其余公司提供的接口服务。这样的话,就对咱们的联调和测试形成了很大的麻烦。假如各个兄弟部门的步伐彻底一致,那么问题就会少不少,但理想很丰满,现实却很骨感,要作到步伐一致基本是不可能的。前端

为此,咱们就须要使用一些工具来帮助咱们将业务单元之间尽可能解耦,它就是Mocknode

什么是Mock

若是将mock单独翻译过来,其意义为 “虚假、虚设”,所以在软件开发领域,咱们也能够将其理解成 “虚假数据”,或者 “真实数据的替身”git

Mock的好处

  • 团队能够更好地并行工做

当使用mock以后,各团队之间能够不须要再互相等待对方的进度,只须要约定好相互之间的数据规范(文档),便可使用mock构建一个可用的接口,而后尽快的进行开发和调试以及自测,提高开发进度的的同时,也将发现缺陷的时间点大大提早。github

  • 开启TDD(Test-Driven Development)模式,即测试驱动开发

单元测试是TDD实现的基石,而TDD常常会碰到协同模块还没有开发完成的状况,可是有了mock,这些一切都不是问题。当接口定义好后,测试人员就能够建立一个Mock,把接口添加到自动化测试环境,提早建立测试。docker

  • 测试覆盖率

好比一个接口在各类不一样的状态下要返回不一样的值,以前咱们的作法是复现这种状态而后再去请求接口,这是很是不科学的作法,并且这种复现方法很大可能性由于操做的时机或者操做方式不当致使失败,甚至污染以前数据库中的数据。若是咱们使用mock,就彻底不用担忧这些问题。数据库

  • 方便演示

经过使用Mock模拟数据接口,咱们便可在只开发了UI的状况下,无须服务端的开发就能够进行产品的演示。npm

  • 隔离系统

在使用某些接口的时候,为了不系统中数据库被污染,咱们能够将这些接口调整为Mock的模式,以此保证数据库的干净。json

在吹了这么多的Mock以后,相信你们必定跃跃欲试了,那么接下来咱们谈一谈实现Mock的几种方法。后端

实现Mock

“倔强青铜”

好了,咱们先从最倔强的“青铜”开始吧,在没有mock的时候,咱们是如何在没有真实接口的状况下进行开发的呢?

在本人的记忆里,当遇到这种状况,我最开始的作法就是将数据先写死在业务中,好比:

// api
import api from '../api/index';

function getApiMessage() {
    return new Promise((resolve) => {
        resolve({
            message: '请求成功'
        });
    })
    // return api.getApiMessage();
}
复制代码

我会将真实的请求注释掉,return一个resolve假数据的promise代替真实的请求,而后我在调用这个方法的时候就会返回一个resolve我本身定义的虚假数据的promise而不是从还没有完成的接口得到的promise。看起来还不错,起码我可以在没有接口的状况下继续进行开发了。虽然当遇到复杂的列表数据的时候,本身写起来有点手疼。

可是虚假数据和业务如此耦合真的好吗?假如当真正的接口完成以后,由于业务能够“正确运行”而忘记了移除这些虚假数据,致使实际你使用的数据一直是你本身编造而非真实的,那但是至关严重的问题。因此咱们接下来须要思考的即是如何尽可能的减小在业务代码中写入这些虚假数据。为了达成这个目标,让咱们正式晋级mock的“荣耀黄金”段位。

“荣耀黄金”

在mock的“荣耀黄金”段位,咱们拥有了一个很是好用的工具:mockJs,经过使用mockJs咱们能根据模板和规则生成复杂的接口数据,而无需咱们本身动手去书写,例如:

// api
import api from '../api/index';
import Mock from 'mockjs';

function getApiMessage() {
    return new Promise((resolve) => {
        resolve(Mock.mock({
            list|1-20: ['mock数据']
        });
    })
    // return api.getApiMessage();
}
/**
* 经过 Mock.mock 方法和 list|1-20: ['mock数据'] 模板
* 咱们将生成一个长度为 1-20, 每一个值都为 'mock数据' 数组
*/
复制代码

可是这样作始终只不过是方便了咱们“造假”而已,并不能将“假货”真的从咱们的业务代码中移除出去。为了实现这个目的,咱们不妨先来分析咱们的需求:

  • 模拟数据与业务代码彻底分离
  • 经过一些配置,达到只mock部分数据,大部分的数据仍是从请求中获取

首先,若是咱们要想要模拟数据和业务代码彻底分离,咱们必需要想办法在请求的时候作一些文章,让其在请求的时候去获取mock数据而非去请求真正的接口,也就是所谓的“请求拦截”,而实现请求拦截也一样有两种方式:

  • 修改请求连接到mock-server,在mock-server配置mock数据和路由
// api/index.js
// 经过新增getDataUseMock方法来讲明使用了mock方法
import request from '../request';

function getDataUseMock(data) {
    request({
        mock: true
    });
}
// request/index.js
const mockServer = 'http://127.0.0.1:8081';

function request(opt) {
    if (opt.mock) {
        const apiName = opt.api;
        opt.url = `${mockServer}/${apiName}`;
    }
    ...
}
复制代码
  • 直接在检测使用mock时,从mock数据文件中取出对应key值的数据
// api/index.js
// 经过新增getDataUseMock方法来讲明使用了mock方法
import request from '../request';

function getDataUseMock(data) {
    request({
        mock: true
    });
}

// request/index.js
import mockData from 'mock/db.js';

function request(opt) {
    if (opt.mock) {
        const apiName = opt.api;
        return new Promise((resolve) => {
            resolve(mockData.apiName)
        })
    }
    ...
}

//mock/db.js
export default {
    '/api/test': {
        msg: '请求成功'
    }
}
复制代码

乍一看好像第二种方式彷佛更简单,事实也确实如此,可是考虑到若是我是直接从文件中直接读取数据,那么业务上的行为也会改变,该发请求的地方并无发请求,因此我仍是选择了本身搭建一个本地的服务,经过控制路由返回不一样的mock数据来处理,而且经过为请求增长一个额外mock参数通知业务哪些接口应当被自建的mock-server拦截,从而尽可能减小对原有业务的影响。

mock-server开发以前,咱们须要明白咱们的mock-server应当能作哪些事情:

  • 所改即所得,具备热更新的能力,每次增长 /修改 mock 接口时不须要重启 mock 服务,更不用重启前端构建服务
  • mock 数据能够由工具生成不须要本身手动写
  • 能模拟 POST、GET 请求

由于mock的模拟数据都在本地维护,咱们所须要的只要是个无界面的可以响应请求的server便可,因此我选择了json-server

在构建server以前,咱们先要明确咱们须要模拟的数据是什么,以及用什么(mockjs)去维护

// db.js
var Mock = require('mockjs');

// 经过使用mock.js,来避免手写数据
module.exports = {
  getComment: Mock.mock({
    "error": 0,
    "message": "success",
    "result|40": [{
      "author": "@name",
      "comment": "@cparagraph",
      "date": "@datetime"
    }]
  })
};
复制代码

其次咱们要知道咱们跳转的访问路由是哪些:

// routes.js
// 根据db.js中的key值,自动生成的路由即是/[key],在route.js中的声明只是为了重定向
module.exports = {
  "/comment/get": "/getComment"
}
复制代码

而后咱们就能够书写咱们启动server的主要代码了:

// server.js
const jsonServer = require('json-server')
const db = require('./db.js')
const routes = require('./routes.js')
const port = 3000;

const server = jsonServer.create()
// 使用mock的数据生成对应的路由
const router = jsonServer.router(db)
const middlewares = jsonServer.defaults()
// 根据路由列表重写路由
const rewriter = jsonServer.rewriter(routes)

server.use(middlewares)
// 将 POST 请求转为 GET,知足能够接受 POST 和 GET 请求的需求
server.use((request, res, next) => {
  request.method = 'GET';
  next();
})

server.use(rewriter) // 注意:rewriter 的设置必定要在 router 设置以前
server.use(router)

server.listen(port, () => {
  console.log('open mock server at localhost:' + port)
})
复制代码

由此,只要使用node server.js便可以启动一个mock-server了,可是这样启动的server,并不能由于我修改route.js或者db.js而实时更新,也就是说,我须要每次都重启一次才能更新个人server,这里还须要咱们进行一个小操做,好比使用nodemon来监控咱们的mock-server.

// 将全部和mock相关的文件:db.js route.js server.js 放入mock文件夹
// 而后执行:
$ nodemon --watch mock mock/server.js

// 就可以启动一个能自动热更新的mock-server了。
复制代码

这以后,咱们只须要在本身的业务代码中,使用咱们以前定义的相似于getDataUseMock的方法,就能够对指定API进行mock啦。

虽然咱们这样作已经完成了mock数据和业务代码的彻底分离,可是仍是不可避免的在业务代码中使用了特殊的方法来声明我须要mock某个接口,仍是一样要面对当不须要mock时,要删除这些方法并替换成正式请求的方法的问题。并且mock数据的部分仍然放在和业务代码一个git目录下,只有开发者才有权限去修改和增长,并无很好地达到mock应当有的做用。

为此,我征求了部门Leader和“广大”开发者的意见,肯定了咱们须要的mock应当是怎样的:

  • 尽可能少的修改业务中的代码就能使用mock
  • 修改的业务代码不会影响正常的业务流程
  • mock-server 应当是面向全部人,而不仅是前端开发者
  • 可以可视化的修改和增长 mock 接口和 mock 数据
  • 可以同时支持多个项目使用

在这几个基本原则的帮助下,咱们的mock终于晋级到了“永恒钻石”段位。

“永恒钻石”

在钻石段位的加持下,我找到了 mock-server 的“上分利器”: 来自阿里前端团队开源的THX工具库中的RAP2,其包含的优点彻底符合我对mock的需求。在依照网上的教程,将RAP2部署到了咱们本地的服务器上以后,咱们只须要经过在本地配置 hosts 文件便可访问咱们本身的RAP2,这以后,咱们须要作的仅仅只剩下业务代码中的处理了:

  • 尽可能少的修改业务中的代码就能使用mock
  • 修改的业务代码不会影响正常的业务流程

为了可以尽可能少的去修改代码而且让修改的代码不影响正常的业务流程,咱们须要增长一个特殊的开发模式,仅在这个开发模式下,咱们修改的代码才会生效,或者说才会存在。

咱们给咱们新增的开发模式能够命名为mock开发模式,为了区分这个开发模式,咱们使用nodejs中的环境变量来进行区分。

"scripts": {
    "dev:mock": "cross-env MOCK=true npm run dev"
}
复制代码

在使用cross-env声明了环境变量以后,咱们能够经过process.env.MOCK获取到咱们声明的环境变量的值,当咱们增长的MOCK变量存在,且为true时,咱们才进行mock的请求拦截。

可是咱们仅仅声明这一点仍是不够,咱们还须要通知业务代码,哪些接口须要被mock。因此,咱们还须要一个mock模式下才会存在的列表,来告诉咱们哪些接口应当被mock。

// config.js
if (process.env.MOCK) {
    config.mockList = [
        '/api/test',
        '/api/needMock'
    ]
} else {
    config.mockList = [];
}
复制代码

固然你也可使用条件编译来判断是否将config.mockList打入你的代码里,这是更加好的选择。

接下来,你只须要在你封装的请求方法里,对config的mockList和你当前请求的api进行对比,判断其是否要进行mock便可。

import config from '../config/config';

const mockServer = 'http://rap2.xxx.com'

function request(opt) {
    const apiName = opt.api;
    if (config.mockList && config.mockList.includes(apiName)) {
        opt.url = `${mockServer}/${apiName}`;
    }
    ...
}
复制代码

如此,咱们的mock终于到达了最终形态,今后只要接口文档(甚至RAP2的mock接口就能够直接做为接口文档),咱们就能随意的进行开发测试啦~

RAP2的使用

从团队开始

团队是仓库的上级单位,一个团队能够拥有多个mock仓库,可是不是只有团队才能拥有仓库,我的也能够。使用团队的目的只是为了让团队下的仓库不被团队外人员获悉,保持一个团队的私密性(固然你也能够选择公开团队)。

仓库

仓库是接口的上级单位,能够归属于我的或者团队,每一个仓库均可以指派开发人员,被指定的人员能够修改或者添加仓库的接口,未被指派的人员仅能查看接口,每一个仓库都拥有一个特定的仓库域名前缀。其下的接口域名规则都遵循:${仓库前缀域名}${接口配置域名},且每一个仓库都提供一个接口获取当前仓库数据。

接口

咱们先来看看接口配置页面的组成:

image
能够看到接口页面主要由以下部分组成:

  • 新建接口(接口列表)
  • 接口模块
  • 接口详情(请求参数和响应参数)

在接口详情中,请求的mock接口的路由是在新建接口的时候去建立的,建立以后自动生成一个接口,请求地址就是${仓库域名}${接口路由}

请求参数的部分配置咱们最主要要关注的是生成规则和默认值,其规则和模板能够参考mockJs文档中的语法规范,生成规则遵循数据模板定义规范(Data Template Definition,DTD),默认值遵循数据占位符定义规范(Data Placeholder Definition,DPD)

引用内容

相关文章
相关标签/搜索