測(cè)試開(kāi)發(fā)工程師必備技能分享:Mock的使用技巧
點(diǎn)擊上方“測(cè)試開(kāi)發(fā)技術(shù)”,選擇“加為星標(biāo)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)!

1. 背景
在實(shí)際產(chǎn)品開(kāi)發(fā)過(guò)程中,某個(gè)服務(wù)或前端依賴一個(gè)服務(wù)接口,該接口可能依賴多個(gè)底層服務(wù)或模塊,或第三方接口,比如說(shuō)服務(wù) A 依賴服務(wù)B,服務(wù)B又依賴服務(wù) C,如下圖所示:
這種依賴的問(wèn)題會(huì)導(dǎo)致原本的需求目的是要驗(yàn)證服務(wù)A,但由于所依賴的服務(wù)B或者服務(wù)C不穩(wěn)定或者未開(kāi)發(fā)完成,導(dǎo)致工作無(wú)法正常開(kāi)展。
那作為測(cè)試工程師,面對(duì)這樣的情形,我們?cè)撛趺崔k呢?解決這類問(wèn)題的核心的思路:引入依賴服務(wù)替身,更通俗的叫法,引入Mock服務(wù)。
今天就結(jié)合unittest框架,給大家分享一些關(guān)于Mock的一些常見(jiàn)使用。
2. Mock是什么
可能還有些讀者之前并沒(méi)有接觸過(guò)Mock,不清楚Mock是個(gè)啥。
Mock簡(jiǎn)單來(lái)理解,就是在測(cè)試過(guò)程中,對(duì)于某些不容易構(gòu)造或者不容易獲取的對(duì)象,用一個(gè)虛擬的對(duì)象來(lái)創(chuàng)建以便測(cè)試。而這個(gè)虛擬的對(duì)象就是mock對(duì)象。mock對(duì)象就是真實(shí)對(duì)象在調(diào)試期間的代替品。
有時(shí)也將Mock服務(wù)稱之為測(cè)試服務(wù)替身,或者測(cè)試服務(wù)檔板,下圖很形象的描述了Mock的作用。
3. Mock能做什么
就Mock功能而言,本身適用場(chǎng)景較多,但在實(shí)際項(xiàng)目中,引入Mock常用來(lái)解決的幾類,概括起來(lái),主要有:
-
接口間的相互依賴 -
單元測(cè)試 -
第三方接口調(diào)用
1.前后端聯(lián)調(diào)
比如你是一個(gè)前端頁(yè)面開(kāi)發(fā),現(xiàn)在需要開(kāi)發(fā)一個(gè)功能:下一個(gè)訂單,支付頁(yè)面的接口,根據(jù)支付結(jié)果,支付成功,展示支付成功頁(yè),支付失敗,展示支付失敗頁(yè)。要完成此功能,你需要調(diào)用后端的接口,根據(jù)返回給你的結(jié)果,來(lái)展示不同的頁(yè)面。此時(shí)后端接口還沒(méi)開(kāi)發(fā)好,作為一個(gè)前端開(kāi)發(fā)總不能等別人開(kāi)發(fā)好了,你再開(kāi)發(fā),那你只有加班的命了。為了同步開(kāi)發(fā)完成任務(wù),此時(shí),你可以根據(jù)接口文檔的規(guī)定,把接口的地址和入?yún)鬟^(guò)去,然后自己mock接口的不同返回界面,來(lái)完成前端的開(kāi)發(fā)任務(wù)。
2.單元測(cè)試
由于單元測(cè)試僅針對(duì)當(dāng)前單元進(jìn)行測(cè)試,這就要求所有的內(nèi)部或者外部依賴都應(yīng)該是穩(wěn)定的,采用mock的方法模擬跟本單元依賴的其他單元,可以將測(cè)試重點(diǎn)放在當(dāng)前單元功能,排除外界因素干擾,提升測(cè)試精準(zhǔn)度。
3.第三方接口依賴
在做接口自動(dòng)化的時(shí)候,有時(shí)候需要調(diào)用第三方的接口,但是別人公司的接口服務(wù)不受你的控制,有可能別人提供的測(cè)試環(huán)境今天服務(wù)給你開(kāi)著,別人就關(guān)掉了,給自動(dòng)化接口測(cè)試帶來(lái)很多的麻煩,此時(shí)就可以通過(guò)mock來(lái)模擬接口的返回?cái)?shù)據(jù),比如模擬各種第三方異常時(shí)的返回。
4. Mock實(shí)現(xiàn)方式
Mock雖然是作為依賴服務(wù)的替身,但并不需要原原本本去構(gòu)造實(shí)現(xiàn)一個(gè)完整的服務(wù)邏輯,比如現(xiàn)在有一個(gè)A服務(wù)依賴B服務(wù),需要通過(guò)Mock來(lái)替換B服務(wù)(做一個(gè)假的B服務(wù)替身)。
那么我們做一個(gè) Mock 服務(wù)其實(shí)就是做了一個(gè)簡(jiǎn)單的服務(wù) B,它不需要實(shí)現(xiàn)原有服務(wù) B 負(fù)載的處理邏輯,只要能按服務(wù)A需要服務(wù)B返回的處理邏輯給出對(duì)應(yīng)返回?cái)?shù)據(jù)就可以了。
目前常見(jiàn)服務(wù)或接口協(xié)議主要兩種,一種是RPC,另一種是HTTP/HTTPS,mock原理都類似,要么是修改原服務(wù)地址為Mock服務(wù)地址,要么是攔截原服務(wù)的請(qǐng)求Mock返回值,總之就是構(gòu)造一個(gè)假的服務(wù),替代原有服務(wù)。
5. Mock市面上常見(jiàn)的解決方案
如果你不想自己動(dòng)手構(gòu)建一套Mock解決方案,市面上也提供了很多現(xiàn)存的Mock方案。常用的有:EasyMock、Mockito 、WireMock、JMockit、Mock、Moco。
如果你團(tuán)隊(duì)技術(shù)基礎(chǔ)相對(duì)比較薄弱,推薦你看看Moco這個(gè)方案,官網(wǎng)如下:
https://github.com/dreamhead/moco/
接下來(lái),重點(diǎn)介紹Python系下Mock方案的使用。
6. Python下unittest.mock使用
unittest.mock是一個(gè)用于在Python中進(jìn)行單元測(cè)試的庫(kù),顧名思義這個(gè)庫(kù)的主要功能是模擬一些東西。它的主要功能是使用mock對(duì)象替代掉指定的Python對(duì)象,以達(dá)到模擬對(duì)象的行為。
需要注意的是在Python2.x版本中,Mock需要單獨(dú)安裝
pip install -U mock
從Python 3.3以后的版本mock已經(jīng)合并到unittest模塊中了,是unittest單元測(cè)試的一部分,直接導(dǎo)入過(guò)來(lái)就行
from unittest import mock
官方文檔:
https://docs.python.org/dev/library/unittest.mock.html
unittest.mock模塊中最常用的是Mock類。
Mock類庫(kù)是一個(gè)專門用于在unittest過(guò)程中制作(偽造)和修改(篡改)測(cè)試對(duì)象的類庫(kù),避免這些對(duì)象在單元測(cè)試過(guò)程中依賴外部資源(網(wǎng)絡(luò)資源,數(shù)據(jù)庫(kù)連接,其它服務(wù)以及耗時(shí)過(guò)長(zhǎng)等)
案例:如下場(chǎng)景:支付是一個(gè)獨(dú)立的接口,由其它開(kāi)發(fā)提供,根據(jù)支付的接口返回狀態(tài)去顯示失敗,還是成功,這個(gè)是你需要實(shí)現(xiàn)的功能,代碼存放在pay.py腳本中:
# !/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author : Mike Zhou
# @Email : 公眾號(hào):測(cè)試開(kāi)發(fā)技術(shù)
# @File : pay.py
def zhifu():
'''假設(shè)這里是一個(gè)支付的功能,未開(kāi)發(fā)完
支付成功返回:{"result": "success", "msg":"支付成功"}
支付失敗返回:{"result": "fail", "msg":"余額不足"}
'''
pass
def zhifu_statues():
'''根據(jù)支付的結(jié)果success or fail,判斷跳轉(zhuǎn)到對(duì)應(yīng)頁(yè)面'''
result = zhifu()
try:
if result["result"] == "success":
return "支付成功"
elif result["result"] == "fail":
return "支付失敗"
else:
return "未知錯(cuò)誤異常"
except:
return "Error, 服務(wù)端返回異常!"
在zhifu_statues方法中,依賴了zhifu方法,但由于zhifu支付方法的接口是由另外一個(gè)同事開(kāi)發(fā),正常情況下,你同事開(kāi)發(fā)的進(jìn)度你是無(wú)法控制的,需要等他開(kāi)發(fā)完了你才能進(jìn)行聯(lián)調(diào)你所負(fù)責(zé)的zhifu_statues接口,因此我們可以通過(guò)引入Mock來(lái)解決這個(gè)問(wèn)題。
引入mock后單元測(cè)試用例代碼
# !/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author : Mike Zhou
# @Email : 公眾號(hào):測(cè)試開(kāi)發(fā)技術(shù)
import unittest
from unittest import mock
import pay
class TestZhifuStatues(unittest.TestCase):
'''單元測(cè)試用例'''
def test_01(self):
'''測(cè)試支付成功場(chǎng)景'''
# mock一個(gè)支付成功的數(shù)據(jù)
pay.zhifu = mock.Mock(return_value={"result": "success", "msg":"支付成功"})
# 根據(jù)支付結(jié)果測(cè)試頁(yè)面跳轉(zhuǎn)
statues = pay.zhifu_statues()
print(statues)
self.assertEqual(statues, "支付成功")
def test_02(self):
'''測(cè)試支付失敗場(chǎng)景'''
# mock一個(gè)支付失敗的數(shù)據(jù)
pay.zhifu = mock.Mock(return_value={"result": "fail", "msg": "余額不足"})
# 根據(jù)支付結(jié)果測(cè)試頁(yè)面跳轉(zhuǎn)
statues = pay.zhifu_statues()
print(statues)
self.assertEqual(statues, "支付失敗")
if __name__ == "__main__":
unittest.main()
上述代碼引入Mock后,我們就可以順利完成對(duì)支付成功和支付異常兩類場(chǎng)景的驗(yàn)證工作。(實(shí)際你可以補(bǔ)充更多)
mock中還有另一種實(shí)現(xiàn)方式,通過(guò)patch裝飾器的使用,patch作為函數(shù)裝飾器,為您創(chuàng)建模擬并將其傳遞到裝飾函數(shù)。
用mock.patch實(shí)現(xiàn)如下:
# !/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author : Mike Zhou
# @Email : 公眾號(hào):測(cè)試開(kāi)發(fā)技術(shù)
import unittest
from unittest import mock
import pay
class TestZhifuStatues(unittest.TestCase):
'''單元測(cè)試用例'''
@mock.patch("pay.zhifu")
def test_001(self, mock_zhifu):
'''測(cè)試支付成功場(chǎng)景'''
# 方法一:mock一個(gè)支付成功的數(shù)據(jù)
# pay.zhifu = mock.Mock(return_value={"result": "success", "msg":"支付成功"})
# print(pay.zhifu())
# 方法二:mock.path裝飾器模擬返回結(jié)果
mock_zhifu.return_value = {"result": "success", "msg":"支付成功"}
# # 根據(jù)支付結(jié)果測(cè)試頁(yè)面跳轉(zhuǎn)
statues = pay.zhifu_statues()
print(statues)
self.assertEqual(statues, "支付成功")
@mock.patch("pay.zhifu")
def test_002(self, mock_zhifu):
'''測(cè)試支付失敗場(chǎng)景'''
# mock一個(gè)支付失敗的數(shù)據(jù)
mock_zhifu.return_value = {"result": "fail", "msg": "余額不足"}
# 根據(jù)支付結(jié)果測(cè)試頁(yè)面跳轉(zhuǎn)
statues = pay.zhifu_statues()
self.assertEqual(statues, "支付失敗")
if __name__ == "__main__":
unittest.main()
還有更多的使用技巧,篇符有限,今天就先分享到這,如果覺(jué)得有用,幫忙點(diǎn)個(gè)好看或朋友圈轉(zhuǎn)發(fā)分享一下就行!
(點(diǎn)擊直達(dá)小程序)
END
長(zhǎng)按二維碼/微信掃碼 關(guān)注

