為什麼需要Mock
假設現在系統有兩個模型A和B,其中A依賴B(例如A,B都是函數,A函數體內調用了B函數),但是B還沒完成,或者根本就不在控制之內;這時候又需要對A的功能進行單獨測試,就需要使用mock對象,類比出一個假的fake_B模組,雖然這個fake_B模組是假的,但是我們可以通過對它的行為進行定製來使他能夠看起來“像”B模組的功能,使A依賴fake_B,來對A的功能進行測試。同時,由於fake_B是完全可控的,除了可以定製B的屬性,傳回值之外,還可以對B模組的使用方式進行測試。
而在Python中可以使用mock和unittest.mock來產生這樣的mock對象。 用法簡介
首先需要建立一個mock對象
>>>from mock import MagicMock >>>fake_obj = MagicMock()
然後可以通過return_value設定它的傳回值,當你調用這個mock對象時返回,傳回值可以是任何類型,變數,函數,類對象都可以。
>>>fake_obj.return_value = 'This is a mock object' >>>fake_obj() 'This is a mock object'
也可以通過side_effect指定它的副作用,這個副作用就是當你調用這個mock對象是會調用的函數,也可以選擇拋出一個異常,來對程式的錯誤狀態進行測試。
>>>def b(): ... print 'This is b' ... >>>fake_obj.side_effect = b >>>fake_obj() This is b >>>fake_obj.side_effect = KeyError('This is b') >>>fake_obj() ... KeyError: 'This is b'
同時也可以以一個對象base為基礎,拓展mock,使得mock獲得base的所有屬性和方法(但僅是介面相同,沒有實現),當調用不屬於base的屬性和方法時,會拋出AttributeError。這需要在建立mock對象的時候,在spec參數指定,前面的傳回值和副作用也可以在建立時進行指定。
>>>class B: ... def __init__(self): ... self.a = 'a' ... >>>b = B() >>>fake_obj = MagicMock(b) <MagicMock spec='B' id='4370614160'> >>>fake_obj.a 'a' >>>fake_obj.b AttributeError: Mock object has no attribute 'b'
還有一點需要強調的就是,如果要類比一個對象而不是函數,你可以直接在mock對象上添加屬性和方法,並且每一個添加的屬性都是一個mock對象,也就是說可以對這些屬性進行配置,並且可以一直遞迴的定義下去。
>>>fake_obj.fake_a.return_value = 'This is fake_obj.fake_a' >>>fake_obj.fake_a() 'This is fake_obj.fake_a'
在對mock對象進行一定的配置之後就是使用mock對象進行測試,這個模組採用的‘action-assertion’的方式進行測試,即先運行,再通過斷言進行測試。例如可以測試mock對象是否被調用,調用了幾次,如何被調用等等,mock對象本身提供了豐富的斷言方法,官方文檔中提供了詳盡的描述。
>>>fake_obj = MagicMock(return_value = 1) >>>fake_obj.assert_called() AssertionError: Expected 'None' to have been called. >>>fake_obj() >>>fake_obj.assert_called() >>> >>>fake_obj(1,2,3,key=1) >>>fake_obj.assert_called_with(1,2,3,key=1) >>>
同時mock庫提供了patch函數來簡化mock對象對原對象的替換。patch可以作為裝飾器或者上下文管理器使用,這意味著在裝飾的函數和上下文管理器中,對應的類會被替換為mock對象。
>>>from mock import patch >>>@patch('__main__.SomeClass') ... def function(normal_argument, mock_class): ... print(mock_class is SomeClass) ... >>>function(None) True
參考資料
官方文檔 https://docs.python.org/3/library/unittest.mock.html