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