測試開發工程必備技能之一:Mock的使用

1. 背景

在實際產品開發過程中,某個服務或前端依賴一個服務接口,該接口可能依賴多個底層服務或模塊,或第三方接口,比如說服務 A 依賴服務B,服務B又依賴服務 C,如下圖所示:


image

這種依賴的問題會導致原本的需求目的是要驗證服務A,但由于所依賴的服務B或者服務C不穩定或者未開發完成,導致工作無法正常開展。


image

那作為測試工程師,面對這樣的情形,我們該怎么辦呢?解決這類問題的核心的思路:引入依賴服務替身,更通俗的叫法,引入Mock服務。

今天就結合unittest框架,給大家分享一些關于Mock的一些常見使用。

2. Mock是什么

可能還有些讀者之前并沒有接觸過Mock,不清楚Mock是個啥。

Mock簡單來理解,就是在測試過程中,對于某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試。而這個虛擬的對象就是mock對象。mock對象就是真實對象在調試期間的代替品。

有時也將Mock服務稱之為測試服務替身,或者測試服務檔板,下圖很形象的描述了Mock的作用。


image

3. Mock能做什么

就Mock功能而言,本身適用場景較多,但在實際項目中,引入Mock常用來解決的幾類,概括起來,主要有:

  • 接口間的相互依賴
  • 單元測試
  • 第三方接口調用

1.前后端聯調

比如你是一個前端頁面開發,現在需要開發一個功能:
下一個訂單,支付頁面的接口,根據支付結果,支付成功,展示支付成功頁,支付失敗,展示支付失敗頁。要完成此功能,你需要調用后端的接口,根據返回給你的結果,來展示不同的頁面。此時后端接口還沒開發好,作為一個前端開發總不能等別人開發好了,你再開發,那你只有加班的命了。為了同步開發完成任務,此時,你可以根據接口文檔的規定,把接口的地址和入參傳過去,然后自己mock接口的不同返回界面,來完成前端的開發任務。

2.單元測試

由于單元測試僅針對當前單元進行測試,這就要求所有的內部或者外部依賴都應該是穩定的,采用mock的方法模擬跟本單元依賴的其他單元,可以將測試重點放在當前單元功能,排除外界因素干擾,提升測試精準度。

3.第三方接口依賴

在做接口自動化的時候,有時候需要調用第三方的接口,但是別人公司的接口服務不受你的控制,有可能別人提供的測試環境今天服務給你開著,別人就關掉了,給自動化接口測試帶來很多的麻煩,此時就可以通過mock來模擬接口的返回數據,比如模擬各種第三方異常時的返回。

4. Mock實現方式

Mock雖然是作為依賴服務的替身,但并不需要原原本本去構造實現一個完整的服務邏輯,比如現在有一個A服務依賴B服務,需要通過Mock來替換B服務(做一個假的B服務替身)。


image

那么我們做一個 Mock 服務其實就是做了一個簡單的服務 B,它不需要實現原有服務 B 負載的處理邏輯,只要能按服務A需要服務B返回的處理邏輯給出對應返回數據就可以了。

目前常見服務或接口協議主要兩種,一種是RPC,另一種是HTTP/HTTPS,mock原理都類似,要么是修改原服務地址為Mock服務地址,要么是攔截原服務的請求Mock返回值,總之就是構造一個假的服務,替代原有服務。

image

5. Mock市面上常見的解決方案

如果你不想自己動手構建一套Mock解決方案,市面上也提供了很多現存的Mock方案。
常用的有:EasyMock、MockitoWireMock、JMockitMock、Moco。

如果你團隊技術基礎相對比較薄弱,推薦你看看Moco這個方案,官網如下:

https://github.com/dreamhead/moco/
image

接下來,重點介紹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類。


image

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()

還有更多的使用技巧,篇符有限,今天就先分享到這,如果覺得有用,歡迎關注!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容