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

狂師發表於2020-12-04

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方案。
常用的有:EasyMockMockitoWireMockJMockitMockMoco

如果你團隊技術基礎相對比較薄弱,推薦你看看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()

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

相關文章