unittest.mock --- 入門指南¶
在 3.3 版被加入.
使用 Mock 的方式¶
使用 Mock 來 patching 方法¶
Mock 物件的常見用法包含:
Patching 方法
記錄在物件上的方法呼叫
你可能會想要取代一個物件上的方法,以便檢查系統的另一部分是否使用正確的引數呼叫它:
>>> real = SomeClass()
>>> real.method = MagicMock(name='method')
>>> real.method(3, 4, 5, key='value')
<MagicMock name='method()' id='...'>
一旦我們的 mock 已經被使用(例如在這個範例中的 real.method),它就有了方法和屬性,允許你對其使用方式進行斷言 (assertions)。
一旦 mock 被呼叫,它的 called 屬性將被設定為 True。更重要的是,我們可以使用 assert_called_with() 或 assert_called_once_with() 方法來檢查它是否被使用正確的引數來呼叫。
這個範例測試呼叫 ProductionClass().method 是否導致對 something 方法的呼叫:
>>> class ProductionClass:
... def method(self):
... self.something(1, 2, 3)
... def something(self, a, b, c):
... pass
...
>>> real = ProductionClass()
>>> real.something = MagicMock()
>>> real.method()
>>> real.something.assert_called_once_with(1, 2, 3)
對物件的方法呼叫使用 mock¶
在上一個範例中,我們直接對物件上的方法進行 patch,以檢查它是否被正確呼叫。另一個常見的用法是將一個物件傳遞給一個方法(或受測系統的某一部分),然後檢查它是否以正確的方式被使用。
下面是一個單純的 ProductionClass,含有一個 closer 方法。如果它被傳入一個物件,它就會呼叫此物件中的 close。
>>> class ProductionClass:
... def closer(self, something):
... something.close()
...
因此,為了對此進行測試,我們需要傳遞一個具有 close 方法的物件,並檢查它是否被正確的呼叫。
>>> real = ProductionClass()
>>> mock = Mock()
>>> real.closer(mock)
>>> mock.close.assert_called_with()
我們不必做任何額外的事情來為 mock 提供 'close' 方法,存取 close 會建立它。因此,如果 'close' 並未被呼叫過,在測試中存取 'close' 就會建立它,但 assert_called_with() 就會引發一個失敗的例外。
Mock 類別¶
一個常見的使用案例是在測試的時候 mock 被程式碼實例化的類別。當你 patch 一個類別時,該類別就會被替換為 mock。實例是透過呼叫類別建立的。這代表你可以透過查看被 mock 的類別的回傳值來存取「mock 實例」。
在下面的範例中,我們有一個函式 some_function,它實例化 Foo 並呼叫它的方法。對 patch() 的呼叫將類別 Foo 替換為一個 mock。Foo 實例是呼叫 mock 的結果,因此它是透過修改 mock return_value 來配置的。:
>>> def some_function():
... instance = module.Foo()
... return instance.method()
...
>>> with patch('module.Foo') as mock:
... instance = mock.return_value
... instance.method.return_value = 'the result'
... result = some_function()
... assert result == 'the result'
命名你的 mock¶
為你的 mock 命名可能會很有用。這個名稱會顯示在 mock 的 repr 中,且當 mock 出現在測試的失敗訊息中時,名稱會很有幫助。該名稱也會傳播到 mock 的屬性或方法:
>>> mock = MagicMock(name='foo')
>>> mock
<MagicMock name='foo' id='...'>
>>> mock.method
<MagicMock name='foo.method' id='...'>
追蹤所有呼叫¶
通常你會想要追蹤對一個方法的多個呼叫。mock_calls 屬性記錄對 mock 的子屬性以及其子屬性的所有呼叫。
>>> mock = MagicMock()
>>> mock.method()
<MagicMock name='mock.method()' id='...'>
>>> mock.attribute.method(10, x=53)
<MagicMock name='mock.attribute.method()' id='...'>
>>> mock.mock_calls
[call.method(), call.attribute.method(10, x=53)]
如果你對 mock_calls 做出斷言並且有任何不預期的方法被呼叫,則斷言將失敗。這很有用,因為除了斷言你期望的呼叫已經進行之外,你還可以檢查它們是否按正確的順序進行,並且沒有多餘的呼叫:
你可以使用 call 物件來建構串列以與 mock_calls 進行比較:
>>> expected = [call.method(), call.attribute.method(10, x=53)]
>>> mock.mock_calls == expected
True
然而,回傳 mock 的呼叫的參數不會被記錄,這代表在巢狀呼叫中,無法追蹤用於建立上代的參數 important 的值: