python之mock模組基本使用
mock簡介
mock原是python的第三方庫
python3以後mock模組已經整合到了unittest測試框架中,不用再單獨安裝
Mock這個詞在英語中有模擬的意思,因此我們可以猜測出這個庫的主要功能是模擬一些東西
準確的說,Mock是Python中一個用於支援單元測試的庫,它的主要功能是使用mock物件替代掉指定的Python物件,以達到模擬物件的行為
既然mock已經被整合到了unittest單元測試框架中,可想而知mock的目的就是為了讓我們更好的進行測試
mock作用
1. 解決依賴問題:當我們測試一個介面或者功能模組的時候,如果這個介面或者功能模組依賴其他介面或其他模組,那麼如果所依賴的介面或功能模組未開發完畢,那麼我們就可以
使用mock模擬被依賴介面,完成目標介面的測試
2. 單元測試:如果某個功能未開發完成,我們又要進行測試用例的程式碼編寫,我們也可以先模擬這個功能進行測試
3. 模擬複雜業務的介面:實際工作中如果我們在測試一個介面功能時,如果這個介面依賴一個非常複雜的介面業務,那麼我們完全可以使用mock來模擬這個複雜的業務介面,其實
這個和解決介面依賴是一樣的原理
4.前後端聯調:如果你是一個前端頁面開發,現在需要開發一個功能:根據後臺返回的狀態展示不同的頁面,那麼你就需要呼叫後臺的介面,但是後臺介面還未開發完成,是不是你
就停止這部分工作呢?答案是否定的,你完全可以藉助mock來模擬後臺這個介面返回你想要的資料
mock安裝
python 3 的mock模組已經被整合到了unittest框架中,所以你使用的時候只需要在檔案開頭from unittest import mock 匯入即可
如果你使用的是python2 那麼你需要執行pip install mock安裝後再 import mock即可
mock例項
一個未開發完成的功能如何測試?
假如們現在有一個實現兩個數相加的功能需要編寫測試用例,但是由於開發進度緩慢,只搭兩個簡單的框架,並沒有內部實現
""" ------------------------------------ @Time : 2019/6/26 14:09 @Auth : linux超 @File : ClassFunc.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : [email protected] @GROUP: 878565760 ------------------------------------ """ import unittest from unittest import mockclass SubClass(object): def add(self, a, b): """兩個數相加""" pass class TestSub(unittest.TestCase):
"""測試兩個數相加用例""" def test_sub(self): sub = SubClass() # 初始化被測函式類例項 sub.add = mock.Mock(return_value=10) # mock add方法 返回10 result = sub.add(5, 5) # 呼叫被測函式 self.assertEqual(result, 10) # 斷言實際結果和預期結果 if __name__ == '__main__': unittest.main()
測試結果
. ---------------------------------------------------------------------- Ran 1 test in 0.000s OK Process finished with exit code
測試結果顯示,測試用例執行已經通過
實際上mock模擬add方法的原理是 使用相同的物件方法接收mock的物件(使用sub.add接收),那麼當mock物件被呼叫時(sub.add())就會返回return_value引數對應的資料
這樣一來,表面看起來就是模擬了add方法(這裡只是我個人理解,不對請忽略)
你可以做一個實驗,把用例中的add改成別的名字也一樣可以測試通過
ok,繼續
我們用例編寫完了,而且開發既然也把功能開發完了(要罵街嗎?),既然真實的功能已經可以測試了,那麼我們怎麼在上面用例的基礎上直接測試真實功能呢?
完整的功能如何測試?
我們把用例的程式碼稍做修改
""" ------------------------------------ @Time : 2019/6/26 14:09 @Auth : linux超 @File : ClassFunc.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : [email protected] @GROUP: 878565760 ------------------------------------ """ import unittest from unittest import mock class SubClass(object): def add(self, a, b): """兩個數相加""" return a + b class TestSub(unittest.TestCase): """測試兩個數相加""" def test_sub(self): sub = SubClass() # 初始化被測函式類例項 sub.add = mock.Mock(return_value=10, side_effect=sub.add) # 傳遞side_effect關鍵字引數, 會覆蓋return_value引數值, 使用真實的add方法測試 result = sub.add(5, 11) # 真正的呼叫被測函式 self.assertEqual(result, 16) # 斷言實際結果和預期結果 if __name__ == '__main__': unittest.main()
side_effect引數
程式碼中我們給Mock方法添加了另一個關鍵字引數side_effect = sub.add, 這個引數和return_value 正好相反,當傳遞這個引數的時候return_value 引數就會失效
而side_effect生效,這裡我給的引數值是sub.add 相當於add方法的地址,那麼當呼叫add方法時就會真實的使用add方法,也就達到了我們測試實際的add 方法。
你也可以理解為當傳遞了side_effect引數且值為被測方法地址時,mock就不會起作用
side_effect接收的是一個可迭代序列,當傳遞多個值時,那麼每次呼叫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
當所有值被取完後就會報錯(這個地方有點類似生成器的原理)
存在依賴關係的功能如何測試?
假設有這樣一個場景:我們要測試一個支付介面但是這個支付介面又依賴一個第三方支付介面,那麼第三方支付介面我們暫時沒有許可權使用,那麼我們該如何測試我們自己這個介面呢?
看下面的例項
假設第三方介面和我們自己的支付介面如下
""" ------------------------------------ @Time : 2019/6/26 15:09 @Auth : linux超 @File : PayMent.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : [email protected] @GROUP: 878565760 ------------------------------------ """ 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 # 返回狀態碼 def pay(self, user_id, card, amount): """ 我們自己的支付介面 :param user_id: 使用者id :param card: 卡號 :param amount: 支付金額 :return: """
# 呼叫第三方支付介面 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的,為了模擬實際中我們無法使用的第三方支付介面
編寫測試用例
""" ------------------------------------ @Time : 2019/6/26 15:22 @Auth : linux超 @File : testpay.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : [email protected] @GROUP: 878565760 ------------------------------------ """ 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()
測試輸出結果
....使用者1000支付失敗, 金額不變 使用者1000支付金額10000成功 ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK Process finished with exit code 0
從執行結果可以看出,即使第三方支付介面無法使用,但是我們自己的支付介面仍然測試通過了
也許有人會問,第三方支付都不能用,我們的測試結果是否是有效的呢?
通常我們在測試一個模組的時候,我們是可以認為其他模組的功能是正常的,只針對目標模組進行測試是沒有任何問題的,所以說測試結果也是正確的
其實上述程式碼還可以使用另一種方式來寫
mock物件的方法
""" ------------------------------------ @Time : 2019/6/26 15:22 @Auth : linux超 @File : testpay.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : [email protected] @GROUP: 878565760 ------------------------------------ """ import unittest from unittest import mock from unittest.mock import patch from payment.PayMent import PayApi class TestPayApi(unittest.TestCase): def setUp(self): self.pay = PayApi() @patch.object(PayApi, 'auth') def test_success(self, mock_auth): mock_auth.return_value = {'status_code':'200'} status = self.pay.pay('1000', '12345', '10000') self.assertEqual(status, '支付成功') @patch.object(PayApi, 'auth') def test_fail(self, mock_auth): mock_auth.return_value={'status_code':'500'} status = self.pay.pay('1000', '12345', '10000') self.assertEqual(status, '支付失敗') @patch.object(PayApi, 'auth') def test_error(self, mock_auth): mock_auth.return_value={'status_code':'300'} status = self.pay.pay('1000', '12345', '10000') self.assertEqual(status, '未知錯誤') @patch.object(PayApi, 'auth') def test_exception(self, mock_auth): mock_auth.return_value='200' status = self.pay.pay('1000', '12345', '10000') self.assertEqual(status, 'Error, 伺服器異常!') if __name__ == '__main__': unittest.main()
還有mock一個普通函式,mock多個方法等,這裡先不贅述,寫法和上面例項差不多
最後
mock還有很多自帶的功能方法
且mock功能很強大,也不是一句兩句話就能說完了,本篇文章主要介紹了mock的基本使用方法,甚是簡單,對於實際中如何應用,如何掌握更強大的方法還需自己慢慢