淺談Mock的應(yīng)用

閱讀之前

希望你能有以下基礎(chǔ)抚官,方便閱讀:

  • ECMAScript 6 (ES6)

為什么需要Mock

image

這樣的場景上陕,相信大家會覺得似曾相識桩砰。

現(xiàn)今的業(yè)務(wù)系統(tǒng)已經(jīng)很少是孤立存在的了,尤其對于一個大公司而言释簿,各個部門之間的配合非常密切亚隅,我們或多或少都需要使用兄弟團隊或是其他公司提供的接口服務(wù)。這樣的話庶溶,就對我們的聯(lián)調(diào)和測試造成了很大的麻煩煮纵。假如各個兄弟部門的步伐完全一致懂鸵,那么問題就會少很多,但理想很豐滿行疏,現(xiàn)實卻很骨感匆光,要做到步伐一致基本是不可能的。

為此酿联,我們就需要使用一些工具來幫助我們將業(yè)務(wù)單元之間盡量解耦终息,它就是Mock

什么是Mock

如果將mock單獨翻譯過來,其意義為 “虛假贞让、虛設(shè)”周崭,因此在軟件開發(fā)領(lǐng)域,我們也可以將其理解成 “虛假數(shù)據(jù)”震桶,或者 “真實數(shù)據(jù)的替身”休傍。

Mock的好處

  • 團隊可以更好地并行工作

當使用mock之后征绎,各團隊之間可以不需要再互相等待對方的進度蹲姐,只需要約定好相互之間的數(shù)據(jù)規(guī)范(文檔),即可使用mock構(gòu)建一個可用的接口人柿,然后盡快的進行開發(fā)和調(diào)試以及自測柴墩,提升開發(fā)進度的的同時,也將發(fā)現(xiàn)缺陷的時間點大大提前凫岖。

  • 開啟TDD(Test-Driven Development)模式江咳,即測試驅(qū)動開發(fā)

單元測試是TDD實現(xiàn)的基石,而TDD經(jīng)常會碰到協(xié)同模塊尚未開發(fā)完成的情況哥放,但是有了mock歼指,這些一切都不是問題。當接口定義好后甥雕,測試人員就可以創(chuàng)建一個Mock踩身,把接口添加到自動化測試環(huán)境,提前創(chuàng)建測試社露。

  • 測試覆蓋率

比如一個接口在各種不同的狀態(tài)下要返回不同的值挟阻,之前我們的做法是復(fù)現(xiàn)這種狀態(tài)然后再去請求接口,這是非常不科學的做法峭弟,而且這種復(fù)現(xiàn)方法很大可能性因為操作的時機或者操作方式不當導(dǎo)致失敗附鸽,甚至污染之前數(shù)據(jù)庫中的數(shù)據(jù)。如果我們使用mock瞒瘸,就完全不用擔心這些問題坷备。

  • 方便演示

通過使用Mock模擬數(shù)據(jù)接口,我們即可在只開發(fā)了UI的情況下情臭,無須服務(wù)端的開發(fā)就可以進行產(chǎn)品的演示击你。

  • 隔離系統(tǒng)

在使用某些接口的時候玉组,為了避免系統(tǒng)中數(shù)據(jù)庫被污染,我們可以將這些接口調(diào)整為Mock的模式丁侄,以此保證數(shù)據(jù)庫的干凈惯雳。

在吹了這么多的Mock之后,相信大家一定躍躍欲試了鸿摇,那么接下來我們談一談實現(xiàn)Mock的幾種方法石景。

實現(xiàn)Mock

“倔強青銅”

好了,我們先從最倔強的“青銅”開始吧拙吉,在沒有mock的時候潮孽,我們是如何在沒有真實接口的情況下進行開發(fā)的呢?

在本人的記憶里筷黔,當遇到這種情況往史,我最開始的做法就是將數(shù)據(jù)先寫死在業(yè)務(wù)中,比如:

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

function getApiMessage() {
    return new Promise((resolve) => {
        resolve({
            message: '請求成功'
        });
    })
    // return api.getApiMessage();
}

我會將真實的請求注釋掉佛舱,return一個resolve假數(shù)據(jù)的promise代替真實的請求椎例,然后我在調(diào)用這個方法的時候就會返回一個resolve我自己定義的虛假數(shù)據(jù)的promise而不是從尚未完成的接口獲得的promise∏胱妫看起來還不錯订歪,起碼我能夠在沒有接口的情況下繼續(xù)進行開發(fā)了。雖然當遇到復(fù)雜的列表數(shù)據(jù)的時候肆捕,自己寫起來有點手疼刷晋。

但是虛假數(shù)據(jù)和業(yè)務(wù)如此耦合真的好嗎?假如當真正的接口完成之后慎陵,因為業(yè)務(wù)可以“正確運行”而忘記了移除這些虛假數(shù)據(jù)眼虱,導(dǎo)致實際你使用的數(shù)據(jù)一直是你自己編造而非真實的,那可是相當嚴重的問題席纽。所以我們接下來需要思考的便是如何盡量的減少在業(yè)務(wù)代碼中寫入這些虛假數(shù)據(jù)捏悬。為了達成這個目標,讓我們正式晉級mock的“榮耀黃金”段位胆筒。

“榮耀黃金”

在mock的“榮耀黃金”段位邮破,我們擁有了一個非常好用的工具:mockJs,通過使用mockJs我們能根據(jù)模板和規(guī)則生成復(fù)雜的接口數(shù)據(jù)仆救,而無需我們自己動手去書寫抒和,例如:

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

function getApiMessage() {
    return new Promise((resolve) => {
        resolve(Mock.mock({
            list|1-20: ['mock數(shù)據(jù)']
        });
    })
    // return api.getApiMessage();
}
/**
* 通過 Mock.mock 方法和 list|1-20: ['mock數(shù)據(jù)'] 模板
* 我們將生成一個長度為 1-20, 每個值都為 'mock數(shù)據(jù)' 數(shù)組
*/

但是這樣做始終只不過是方便了我們“造假”而已,并不能將“假貨”真的從我們的業(yè)務(wù)代碼中移除出去彤蔽。為了實現(xiàn)這個目的摧莽,我們不妨先來分析我們的需求:

  • 模擬數(shù)據(jù)與業(yè)務(wù)代碼完全分離
  • 通過一些配置,達到只mock部分數(shù)據(jù)顿痪,大部分的數(shù)據(jù)還是從請求中獲取

首先镊辕,如果我們要想要模擬數(shù)據(jù)和業(yè)務(wù)代碼完全分離油够,我們必須要想辦法在請求的時候做一些文章,讓其在請求的時候去獲取mock數(shù)據(jù)而非去請求真正的接口征懈,也就是所謂的“請求攔截”石咬,而實現(xiàn)請求攔截也同樣有兩種方式:

  • 修改請求鏈接到mock-server,在mock-server配置mock數(shù)據(jù)和路由
// 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數(shù)據(jù)文件中取出對應(yīng)key值的數(shù)據(jù)
// 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: '請求成功'
    }
}

乍一看好像第二種方式似乎更簡單鬼悠,事實也確實如此,但是考慮到如果我是直接從文件中直接讀取數(shù)據(jù)亏娜,那么業(yè)務(wù)上的行為也會改變焕窝,該發(fā)請求的地方并沒有發(fā)請求,所以我還是選擇了自己搭建一個本地的服務(wù)维贺,通過控制路由返回不同的mock數(shù)據(jù)來處理它掂,并且通過為請求增加一個額外mock參數(shù)通知業(yè)務(wù)哪些接口應(yīng)當被自建的mock-server攔截,從而盡量減少對原有業(yè)務(wù)的影響溯泣。

mock-server開發(fā)之前虐秋,我們需要明白我們的mock-server應(yīng)當能做哪些事情:

  • 所改即所得,具有熱更新的能力发乔,每次增加 /修改 mock 接口時不需要重啟 mock 服務(wù)熟妓,更不用重啟前端構(gòu)建服務(wù)
  • mock 數(shù)據(jù)可以由工具生成不需要自己手動寫
  • 能模擬 POST雪猪、GET 請求

因為mock的模擬數(shù)據(jù)都在本地維護栏尚,我們所需要的只要是個無界面的能夠響應(yīng)請求的server即可,所以我選擇了json-server

在構(gòu)建server之前只恨,我們先要明確我們需要模擬的數(shù)據(jù)是什么译仗,以及用什么(mockjs)去維護

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

// 通過使用mock.js,來避免手寫數(shù)據(jù)
module.exports = {
  getComment: Mock.mock({
    "error": 0,
    "message": "success",
    "result|40": [{
      "author": "@name",
      "comment": "@cparagraph",
      "date": "@datetime"
    }]
  })
};

其次我們要知道我們跳轉(zhuǎn)的訪問路由是哪些:

// routes.js
// 根據(jù)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的數(shù)據(jù)生成對應(yīng)的路由
const router = jsonServer.router(db)
const middlewares = jsonServer.defaults()
// 根據(jù)路由列表重寫路由
const rewriter = jsonServer.rewriter(routes)

server.use(middlewares)
// 將 POST 請求轉(zhuǎn)為 GET,滿足可以接受 POST 和 GET 請求的需求
server.use((request, res, next) => {
  request.method = 'GET';
  next();
})

server.use(rewriter) // 注意:rewriter 的設(shè)置一定要在 router 設(shè)置之前
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來監(jiān)控我們的mock-server.

// 將所有和mock相關(guān)的文件:db.js route.js server.js 放入mock文件夾
// 然后執(zhí)行:
$ nodemon --watch mock mock/server.js

// 就能夠啟動一個能自動熱更新的mock-server了忱详。

這之后,我們只需要在自己的業(yè)務(wù)代碼中跺涤,使用我們之前定義的類似于getDataUseMock的方法匈睁,就可以對指定API進行mock啦监透。

雖然我們這樣做已經(jīng)完成了mock數(shù)據(jù)和業(yè)務(wù)代碼的完全分離,但是還是不可避免的在業(yè)務(wù)代碼中使用了特殊的方法來聲明我需要mock某個接口航唆,還是同樣要面對當不需要mock時胀蛮,要刪除這些方法并替換成正式請求的方法的問題。而且mock數(shù)據(jù)的部分仍然放在和業(yè)務(wù)代碼一個git目錄下糯钙,只有開發(fā)者才有權(quán)限去修改和增加醇滥,并沒有很好地達到mock應(yīng)當有的作用。

為此超营,我征求了部門Leader和“廣大”開發(fā)者的意見鸳玩,確定了我們需要的mock應(yīng)當是怎樣的:

  • 盡量少的修改業(yè)務(wù)中的代碼就能使用mock
  • 修改的業(yè)務(wù)代碼不會影響正常的業(yè)務(wù)流程
  • mock-server 應(yīng)當是面向所有人,而不只是前端開發(fā)者
  • 能夠可視化的修改和增加 mock 接口和 mock 數(shù)據(jù)
  • 能夠同時支持多個項目使用

在這幾個基本原則的幫助下演闭,我們的mock終于晉級到了“永恒鉆石”段位不跟。

“永恒鉆石”

在鉆石段位的加持下,我找到了 mock-server 的“上分利器”: 來自阿里前端團隊開源的THX工具庫中的RAP2米碰,其包含的優(yōu)勢完全符合我對mock的需求窝革。在依照網(wǎng)上的教程,將RAP2部署到了我們本地的服務(wù)器上之后吕座,我們只需要通過在本地配置 hosts 文件即可訪問我們自己的RAP2虐译,這之后,我們需要做的僅僅只剩下業(yè)務(wù)代碼中的處理了:

  • 盡量少的修改業(yè)務(wù)中的代碼就能使用mock
  • 修改的業(yè)務(wù)代碼不會影響正常的業(yè)務(wù)流程

為了能夠盡量少的去修改代碼并且讓修改的代碼不影響正常的業(yè)務(wù)流程吴趴,我們需要增加一個特殊的開發(fā)模式漆诽,僅在這個開發(fā)模式下,我們修改的代碼才會生效锣枝,或者說才會存在厢拭。

我們給我們新增的開發(fā)模式可以命名為mock開發(fā)模式,為了區(qū)分這個開發(fā)模式撇叁,我們使用nodejs中的環(huán)境變量來進行區(qū)分供鸠。

"scripts": {
    "dev:mock": "cross-env MOCK=true npm run dev"
}

在使用cross-env聲明了環(huán)境變量之后,我們可以通過process.env.MOCK獲取到我們聲明的環(huán)境變量的值陨闹,當我們增加的MOCK變量存在楞捂,且為true時,我們才進行mock的請求攔截趋厉。

但是我們僅僅聲明這一點還是不夠寨闹,我們還需要通知業(yè)務(wù)代碼,哪些接口需要被mock觅廓。所以鼻忠,我們還需要一個mock模式下才會存在的列表,來告訴我們哪些接口應(yīng)當被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終于到達了最終形態(tài)哨啃,從此只要接口文檔(甚至RAP2的mock接口就可以直接作為接口文檔),我們就能隨意的進行開發(fā)測試啦~

RAP2的使用

從團隊開始

團隊是倉庫的上級單位写妥,一個團隊可以擁有多個mock倉庫拳球,但是不是只有團隊才能擁有倉庫,個人也可以珍特。使用團隊的目的只是為了讓團隊下的倉庫不被團隊外人員獲悉祝峻,保持一個團隊的私密性(當然你也可以選擇公開團隊)。

倉庫

倉庫是接口的上級單位扎筒,可以歸屬于個人或者團隊莱找,每個倉庫都可以指派開發(fā)人員,被指定的人員可以修改或者添加倉庫的接口嗜桌,未被指派的人員僅能查看接口奥溺,每個倉庫都擁有一個特定的倉庫域名前綴。其下的接口域名規(guī)則都遵循:${倉庫前綴域名}${接口配置域名}骨宠,且每個倉庫都提供一個接口獲取當前倉庫數(shù)據(jù)浮定。

接口

我們先來看看接口配置頁面的組成:


image

可以看到接口頁面主要由如下部分組成:

  • 新建接口(接口列表)
  • 接口模塊
  • 接口詳情(請求參數(shù)和響應(yīng)參數(shù))

在接口詳情中,請求的mock接口的路由是在新建接口的時候去創(chuàng)建的诱篷,創(chuàng)建之后自動生成一個接口壶唤,請求地址就是${倉庫域名}${接口路由}雳灵。

請求參數(shù)的部分配置我們最主要要關(guān)注的是生成規(guī)則和默認值棕所,其規(guī)則和模板可以參考mockJs文檔中的語法規(guī)范,生成規(guī)則遵循數(shù)據(jù)模板定義規(guī)范(Data Template Definition悯辙,DTD)琳省,默認值遵循數(shù)據(jù)占位符定義規(guī)范(Data Placeholder Definition,DPD)躲撰。

引用內(nèi)容

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拢蛋,隨后出現(xiàn)的幾起案子桦他,更是在濱河造成了極大的恐慌,老刑警劉巖谆棱,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件快压,死亡現(xiàn)場離奇詭異圆仔,居然都是意外死亡,警方通過查閱死者的電腦和手機蔫劣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門坪郭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脉幢,你說我怎么就攤上這事歪沃。” “怎么了嫌松?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵沪曙,是天一觀的道長。 經(jīng)常有香客問我萎羔,道長珊蟀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任外驱,我火速辦了婚禮育灸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘昵宇。我一直安慰自己磅崭,他們只是感情好,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布瓦哎。 她就那樣靜靜地躺著砸喻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蒋譬。 梳的紋絲不亂的頭發(fā)上割岛,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音犯助,去河邊找鬼癣漆。 笑死,一個胖子當著我的面吹牛剂买,可吹牛的內(nèi)容都是我干的惠爽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼瞬哼,長吁一口氣:“原來是場噩夢啊……” “哼婚肆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起坐慰,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤较性,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赞咙,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡永毅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了人弓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沼死。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖崔赌,靈堂內(nèi)的尸體忽然破棺而出意蛀,到底是詐尸還是另有隱情,我是刑警寧澤健芭,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布县钥,位于F島的核電站,受9級特大地震影響慈迈,放射性物質(zhì)發(fā)生泄漏若贮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一痒留、第九天 我趴在偏房一處隱蔽的房頂上張望谴麦。 院中可真熱鬧,春花似錦伸头、人聲如沸匾效。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽面哼。三九已至,卻和暖如春扫步,著一層夾襖步出監(jiān)牢的瞬間魔策,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工河胎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留闯袒,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓仿粹,卻偏偏與公主長得像搁吓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吭历,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,070評論 25 707
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料擂橘? 從這篇文章中你...
    hw1212閱讀 12,712評論 2 59
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器疆虚,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 感覺今天過得特別快,上午在家里忙著洗衣拖地洗頭發(fā)做飯恼五,下午一點半就出發(fā)來學校,鄰座的小孩一直嘰嘰喳喳個不停哭懈,不停問...
    若水青衫閱讀 203評論 4 1