mock簡介
? ? ? ? mock原是python的第三方庫拴测,python 2可以直接安裝mock模塊糊余,但在python 3.3以后mock模塊已經(jīng)整合到了unittest測試框架中逞壁,不需要再單獨安裝。
????????Mock這個詞在英語中有模擬的這個意思浅悉,因此我們可以猜測出這個庫的主要功能是模擬一些東西胸嘴。準確的說,Mock是Python中一個用于支持單元測試的庫嵌莉,它的主要功能是使用mock對象替代掉指定的Python對象进萄,以達到模擬對象的行為。簡單的說锐峭,mock庫用于如下的場景:
假設你開發(fā)的一個api在工作的時候需要調(diào)用發(fā)送請求給特定的服務器來得到一個JSON返回值中鼠,然后根據(jù)這個返回值來做處理。如果要為該API寫一個單元測試沿癞,該如何做援雇?
一個簡單的辦法是搭建一個測試的服務器,在單元測試的時候椎扬,讓該API和這個測試服務器交互惫搏。但是這種做法有兩個問題:
1、測試服務器可能很不好搭建蚕涤,或者搭建效率很低筐赔。
2、你搭建的測試服務器可能無法返回所有可能的值揖铜,或者需要大量的工作才能達到這個目的茴丰。
那么如何在沒有測試服務器的情況下進行上面這種情況的單元測試呢?Mock模塊就是答案。因為mock模塊可以替換Python對象贿肩,返回值能夠由我們的mock對象來決定峦椰,而不需要服務器的參與。
mock作用:
1. 解決依賴問題:當我們測試一個接口或者功能模塊的時候汰规,如果這個接口或者功能模塊依賴其他接口或其他模塊汤功,那么如果所依賴的接口或功能模塊未開發(fā)完畢,那么我們就可以使用mock模擬被依賴接口溜哮,完成目標接口的測試
2. 單元測試:如果某個功能未開發(fā)完成滔金,我們又要進行測試用例的代碼編寫,我們也可以先模擬這個功能進行測試
3. 模擬復雜業(yè)務的接口:實際工作中如果我們在測試一個接口功能時茂嗓,如果這個接口依賴一個非常復雜的接口業(yè)務鹦蠕,那么我們完全可以使用mock來模擬這個復雜的業(yè)務接口,其實這個和解決接口依賴是一樣的原理
4.前后端聯(lián)調(diào):如果你是一個前端頁面開發(fā)在抛,現(xiàn)在需要開發(fā)一個功能:根據(jù)后臺返回的狀態(tài)展示不同的頁面赫蛇,那么你就需要調(diào)用后臺的接口授翻,但是后臺接口還未開發(fā)完成,是不是你就停止這部分工作呢测僵?答案是否定的票唆,你完全可以借助mock來模擬后臺這個接口返回你想要的數(shù)據(jù)
Mock的安裝和導入
1朴读、在Python 3.3以前的版本中,需要另外安裝mock模塊走趋,可以使用pip命令來安裝:
pip install mock
然后在代碼中就可以直接import進來:
import mock
2衅金、從Python 3.3開始,mock模塊已經(jīng)被合并到標準庫中簿煌,被命名為unittest.mock氮唯,可以直接import進來使用:
from unittest import mock
Mock對象
????Mock對象是mock模塊中最重要的概念。Mock對象就是mock模塊中的一個類的實例姨伟,這個類的實例可以用來替換其他的Python對象惩琉,來達到模擬的效果。Mock類的定義如下:
classMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, **kwargs)
Mock對象的一般用法是這樣的:
1夺荒、找到你要替換的對象瞒渠,這個對象可以是一個類,或者是一個函數(shù)技扼,或者是一個類實例伍玖。
2、然后實例化Mock類得到一個mock對象剿吻,并且設置這個mock對象的行為窍箍,比如被調(diào)用的時候返回什么值,被訪問成員的時候返回什么值等。
3仔燕、使用這個mock對象替換掉我們想替換的對象造垛,也就是步驟1中確定的對象。
4晰搀、之后就可以開始寫測試代碼五辽,這個時候我們可以保證我們替換掉的對象在測試用例執(zhí)行的過程中行為和我們預設的一樣。
mock實例及基本用法
????一個未開發(fā)完成的功能如何測試外恕?假如們現(xiàn)在有一個實現(xiàn)兩個數(shù)相加的功能需要編寫測試用例杆逗,但是由于開發(fā)進度緩慢,只搭兩個簡單的框架鳞疲,并沒有內(nèi)部實現(xiàn)罪郊。
import unittest
from unittest import mock
class SubClass(object):
? ? def add(self, a, b):
? ? ? ? """兩個數(shù)相加"""
? ? ? ? pass
class TestSub(unittest.TestCase):
"""測試兩個數(shù)相加用例"""
? ? def test_sub(self):
? ? ? ? sub = SubClass()? # 初始化被測函數(shù)類實例
? ? ? ? sub.add = mock.Mock(return_value=10)? # mock add方法 返回10
? ? ? ? result = sub.add(5, 5)? # 調(diào)用被測函數(shù)
? ? ? ? self.assertEqual(result, 10)? # 斷言實際結(jié)果和預期結(jié)果
if __name__ == '__main__':
? ? unittest.main()
測試結(jié)果:
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Process finished with exit code?
測試結(jié)果顯示,測試用例執(zhí)行通過了尚洽。
實際上mock模擬add方法的原理是 使用相同的對象方法接收mock的對象(使用sub.add接收)悔橄,那么當mock對象被調(diào)用時(sub.add())就會返回return_value參數(shù)對應的數(shù)據(jù)
稍微高級的用法
1、class Mock的參數(shù)
上面講的是mock對象最基本的用法腺毫。下面來看看mock對象的稍微高級點的用法
先來看看Mock這個類的參數(shù)癣疟,在上面看到的類定義中,我們知道它有好幾個參數(shù)潮酒,這里介紹最主要的幾個:
name: 這個是用來命名一個mock對象睛挚,只是起到標識作用,當你print一個mock對象的時候急黎,可以看到它的name扎狱。
return_value: 這個我們剛才使用過了,這個字段可以指定一個值(或者對象)勃教,當mock對象被調(diào)用時淤击,如果side_effect函數(shù)返回的是DEFAULT,則對mock對象的調(diào)用會返回return_value指定的值荣回。
side_effect: 這個參數(shù)指向一個可調(diào)用對象遭贸,一般就是函數(shù)。當mock對象被調(diào)用時心软,如果該函數(shù)返回值不是DEFAULT時壕吹,那么以該函數(shù)的返回值作為mock對象調(diào)用的返回值。
上面的例子删铃,我們把用例的代碼修改一句如下:
class TestSub(unittest.TestCase):
? ? """測試兩個數(shù)相加"""
? ? def test_sub(self):
? ? ? ? sub = SubClass()? # 初始化被測函數(shù)類實例
? ? ? ? sub.add = mock.Mock(return_value=10, side_effect=sub.add)? # 傳遞side_effect關(guān)鍵字參數(shù), 會覆蓋return_value參數(shù)值, 使用真實的add方法測試
? ? ? ? result = sub.add(5, 11)? # 真正的調(diào)用被測函數(shù)
? ? ? ? self.assertEqual(result, 16)? # 斷言實際結(jié)果和預期結(jié)果
????????代碼中我們給Mock方法添加了另一個關(guān)鍵字參數(shù)side_effect = sub.add耳贬, 這個參數(shù)和return_value 正好相反,當傳遞這個參數(shù)的時候return_value 參數(shù)就會失效
而side_effect生效猎唁,這里我給的參數(shù)值是sub.add 相當于add方法的地址咒劲,那么當調(diào)用add方法時就會真實的使用add方法,也就達到了我們測試實際的add 方法。
你也可以理解為當傳遞了side_effect參數(shù)且值為被測方法地址時腐魂,mock就不會起作用
side_effect接收的是一個可迭代序列帐偎,當傳遞多個值時,那么每次調(diào)用mock時會返回不同的值
mock_obj = mock.Mock(side_effect= [1,2,3])
print(mock_obj())
print(mock_obj())
print(mock_obj())
print(mock_obj())
輸出
Traceback (most recent call last):
1
? File "D:/MyThreading/mymock.py", line 37, in <module>
2
? ? print(mock_obj())
3
? File "C:\Python36\lib\unittest\mock.py", line 939, in __call__
? ? return _mock_self._mock_call(*args, **kwargs)
? File "C:\Python36\lib\unittest\mock.py", line 998, in _mock_call
? ? result = next(effect)
StopIteration
Process finished with exit code 1
當所有值被取完后就會報錯(這個地方有點類似生成器的原理)
2蛔屹、mock對象的自動創(chuàng)建
當訪問一個mock對象中不存在的屬性時削樊,mock會自動建立一個子mock對象,并且把正在訪問的屬性指向它兔毒,這個功能對于實現(xiàn)多級屬性的mock很方便漫贞。
client= mock.Mock()client.v2_client.get.return_value ='200'
這個時候,你就得到了一個mock過的client實例育叁,調(diào)用該實例的v2_client.get()方法會得到的返回值是"200"迅脐。
從上面的例子中還可以看到,指定mock對象的return_value還可以使用屬性賦值的方法豪嗽。
存在依賴關(guān)系的功能如何測試谴蔑?
? ? ? ?假設有這樣一個場景:我們要測試一個支付接口但是這個支付接口又依賴一個第三方支付接口,那么第三方支付接口我們暫時沒有權(quán)限使用龟梦,那么我們該如何測試我們自己這個接口呢树碱?
看下面的實例,假設第三方接口和我們自己的支付接口如下:
import requests
class PayApi(object):
? ? @staticmethod
? ? def auth(card, amount):
? ? ? ? """
? ? ? ? 第三方支付接口
? ? ? ? :param card: 卡號
? ? ? ? :param amount: 支付金額
? ? ? ? :return:
? ? ? ? """
? ? ? ? pay_url = "http://www.zhifubao.com"? # 第三方支付接口地址
? ? ? ? data = {"card": card, "amount": amount}
? ? ? ? response = requests.post(pay_url, data=data)? # 請求第三方支付接口
? ? ? ? return response? # 返回狀態(tài)碼
? ? def pay(self, user_id, card, amount):
? ? ? ? """
? ? ? ? 我們自己的支付接口
? ? ? ? :param user_id: 用戶id
? ? ? ? :param card: 卡號
? ? ? ? :param amount: 支付金額
? ? ? ? :return:
? ? ? ? """
# 調(diào)用第三方支付接口
? ? ? ? response = self.auth(card, amount)
? ? ? ? try:
? ? ? ? ? ? if response['status_code'] == '200':
? ? ? ? ? ? ? ? print('用戶{}支付金額{}成功'.format(user_id, amount))
? ? ? ? ? ? ? ? return '支付成功'
? ? ? ? ? ? elif response['status_code'] == '500':
? ? ? ? ? ? ? ? print('用戶{}支付失敗, 金額不變'.format(user_id))
? ? ? ? ? ? ? ? return '支付失敗'
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? return '未知錯誤'
? ? ? ? except Exception:
? ? ? ? ? ? return "Error, 服務器異常!"
if __name__ == '__main__':
? ? pass
很明顯第三方支付接口是無法訪問的变秦,因為接口的地址是我DIY的,為了模擬實際中我們無法使用的第三方支付接口框舔。編寫測試用例如下:
import unittest
from unittest import mock
from payment.PayMent import PayApi
class TestPayApi(unittest.TestCase):
? ? def test_success(self):
? ? ? ? pay = PayApi()
? ? ? ? pay.auth = mock.Mock(return_value={'status_code':'200'})
? ? ? ? status = pay.pay('1000', '12345', '10000')
? ? ? ? self.assertEqual(status, '支付成功')
? ? def test_fail(self):
? ? ? ? pay = PayApi()
? ? ? ? pay.auth = mock.Mock(return_value={'status_code':'500'})
? ? ? ? status = pay.pay('1000', '12345', '10000')
? ? ? ? self.assertEqual(status, '支付失敗')
? ? def test_error(self):
? ? ? ? pay = PayApi()
? ? ? ? pay.auth = mock.Mock(return_value={'status_code':'300'})
? ? ? ? status = pay.pay('1000', '12345', '10000')
? ? ? ? self.assertEqual(status, '未知錯誤')
? ? def test_exception(self):
? ? ? ? pay = PayApi()
? ? ? ? pay.auth = mock.Mock(return_value='200')
? ? ? ? status = pay.pay('1000', '12345', '10000')
? ? ? ? self.assertEqual(status, 'Error, 服務器異常!')
if __name__ == '__main__':
? ? unittest.main()
測試輸出結(jié)果:
....用戶1000支付失敗, 金額不變
用戶1000支付金額10000成功
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK
Process finished with exit code 0
????從執(zhí)行結(jié)果可以看出蹦玫,即使第三方支付接口無法使用,但是我們自己的支付接口仍然測試通過了刘绣。也許有人會問樱溉,第三方支付都不能用,我們的測試結(jié)果是否是有效的呢纬凤?通常我們在測試一個模塊的時候福贞,我們是可以認為其他模塊的功能是正常的,只針對目標模塊進行測試是沒有任何問題的停士,所以說測試結(jié)果也是正確的挖帘。