淺談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)

引用內容

相關文章
相關標籤/搜索