unittest.mock — mock 物件函式庫

在 3.3 版被加入.

原始碼:Lib/unittest/mock.py


unittest.mock 在 Python 中是一個用於進行測試的函式庫。 它允許你用 mock 物件在測試中替換部分系統,並判定它們是如何被使用的。

unittest.mock 提供了一個以 Mock 為核心的類別,去除在測試中建立大量 stubs 的需求。 在執行動作之後,你可以判定哪些 method (方法)/屬性被使用,以及有哪些引數被呼叫。 你還可以用常規的方式指定回傳值與設定所需的屬性。

此外,mock 還提供了一個 patch() 裝飾器,用於 patching 測試範圍內對 module(模組)以及 class(類別)級別的屬性,以及用於建立唯一物件的 sentinel。有關如何使用 MockMagicMockpatch() 的一些範例,請參閱快速導引

Mock 被設計用於與 unittest 一起使用,並且基於 「action(操作) -> assertion(判定)」 模式,而不是許多 mocking 框架使用的 「record(記錄) -> replay(重播)」 模式。

對於早期版本的 Python,有一個 backport(向後移植的)unittest.mock 可以使用,可從 PyPI 下載 mock

快速導引

MockMagicMock 物件在你存取它們時建立所有屬性和 method(方法),並儲存它們如何被使用的詳細訊息。你可以配置它們,以指定回傳值或限制可用的屬性,然後對它們的使用方式做出判定:

>>> from unittest.mock import MagicMock
>>> thing = ProductionClass()
>>> thing.method = MagicMock(return_value=3)
>>> thing.method(3, 4, 5, key='value')
3
>>> thing.method.assert_called_with(3, 4, 5, key='value')

side_effect 允許你執行 side effects,包含在 mock 被呼叫時引發例外:

>>> from unittest.mock import Mock
>>> mock = Mock(side_effect=KeyError('foo'))
>>> mock()
Traceback (most recent call last):
 ...
KeyError: 'foo'
>>> values = {'a': 1, 'b': 2, 'c': 3}
>>> def side_effect(arg):
...     return values[arg]
...
>>> mock.side_effect = side_effect
>>> mock('a'), mock('b'), mock('c')
(1, 2, 3)
>>> mock.side_effect = [5, 4, 3, 2, 1]
>>> mock(), mock(), mock()
(5, 4, 3)

Mock 有許多其他方法可以讓你配置與控制它的行為。例如,spec 引數可以配置 mock ,讓其從另一個物件取得規格。嘗試讀取 mock 中不存在於規格中的屬性或方法將會失敗,並出現 AttributeError

patch() 裝飾器/情境管理器可以在測試中簡單的 mock 模組中的類別或物件。被指定的物件在測試期間會被替換為 mock(或其他物件),並在測試結束時恢復:

>>> from unittest.mock import patch
>>> @patch('module.ClassName2')
... @patch('module.ClassName1')
... def test(MockClass1, MockClass2):
...     module.ClassName1()
...     module.ClassName2()
...     assert MockClass1 is module.ClassName1
...     assert MockClass2 is module.ClassName2
...     assert MockClass1.called
...     assert MockClass2.called
...
>>> test()

備註

當你巢狀使用 patch 裝飾器時,mock 傳遞到被裝飾函式的順序會跟其被應用的順序相同(一般 Python 應用裝飾器的順序)。這意味著由下而上,因此在上面的範例中,module.ClassName1 的 mock 會先被傳入。

使用 patch() 時,需注意的是你得在被查找物件的命名空間中(in the namespace where they are looked up)patch 物件。這通常很直接,但若需要快速導引,請參閱該 patch 何處

裝飾器 patch() 也可以在 with 陳述式中被用來作為情境管理器:

>>> with patch.object(ProductionClass, 'method', return_value=None) as mock_method:
...     thing = ProductionClass()
...     thing.method(1, 2, 3)
...
>>> mock_method.assert_called_once_with(1, 2, 3)

也有 patch.dict(),用於在測試範圍中設定 dictionary(字典)內的值,並在測試結束時將其恢復為原始狀態:

>>> foo = {'key': 'value'}
>>> original = foo.copy()
>>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):
...     assert foo == {'newkey': 'newvalue'}
...
>>> assert foo == original

Mock 支援對 Python 的魔術方法的 mocking。最簡單使用魔術方法的方式是使用 MagicMock 類別。它允許你執行以下操作:

>>> mock = MagicMock()
>>> mock.__str__.return_value = 'foobarbaz'
>>> str(mock)
'foobarbaz'
>>> mock.__str__.assert_called_with()

Mock 允許你將函式(或其他 Mock 實例)分配給魔術方法,並且它們將被適當地呼叫。MagicMock 類別是一個 Mock 的變體,它為你預先建好了所有魔術方法(好吧,所有有用的方法)。

以下是在一般 Mock 類別中使用魔術方法的範例:

>>> mock = Mock()
>>> mock.__str__ = Mock(return_value='wheeeeee')
>>> str(mock)
'wheeeeee'

為了確保測試中的 mock 物件與它們要替換的物件具有相同的 api,你可以使用自動規格。自動規格(auto-speccing)可以透過 patch 的 autospec 引數或 create_autospec() 函式來完成。自動規格建立的 mock 物件與它們要替換的物件具有相同的屬性和方法,並且任何函式和方法(包括建構函式)都具有與真實物件相同的呼叫簽名(call signature)。

這可以確保如果使用方法錯誤,你的 mock 會跟實際程式碼以相同的方式失敗:

>>> from unittest.mock import create_autospec
>>> def function(a, b, c):
...     pass
...
>>> mock_function = create_autospec(function, return_value='fishy')
>>> mock_function(1, 2, 3)
'fishy'
>>> mock_function.assert_called_once_with(1, 2, 3)
>>> mock_function('wrong arguments')
Traceback (most recent call last):
 ...
TypeError: missing a required argument: 'b'

create_autospec() 也可以用在類別上,它複製了 __init__ 方法的簽名,它也可以用在可呼叫物件上,其複製了 __call__ 方法的簽名。

Mock 類別

Mock 是一個彈性的的 mock 物件,旨在代替程式碼中 stubs 和 test doubles (測試替身)的使用。Mock 是可呼叫的,並在你存取它們時將屬性建立為新的 mock [1]。存取相同的屬性將永遠回傳相同的 mock。Mock 記錄了你如何使用它們,允許你判定你的程式碼對 mock 的行為。

MagicMockMock 的子類別,其中所有魔術方法均已預先建立並可供使用。也有不可呼叫的變體,在你 mock 無法呼叫的物件時很有用:NonCallableMockNonCallableMagicMock

patch() 裝飾器可以輕鬆地用 Mock 物件臨時替換特定模組中的類別。預設情況下,patch() 會為你建立一個 MagicMock。你可以使用 patch()new_callable 引數指定 Mock 的替代類別。

class unittest.mock.Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)

建立一個新的 Mock 物件。Mock 接受數個可選的引數來指定 Mock 物件的行為:

  • spec:這可以是字串的 list(串列),也可以是充當 mock 物件規格的現有物件(類別或實例)。如果傳入一個物件,則透過對該物件呼叫 dir 來形成字串的串列(不包括不支援的魔術屬性和方法)。存取不在此串列中的任何屬性都會引發 AttributeError

    如果 spec 是一個物件(而不是一個字串的串列),那麼 __class__ 會回傳 spec 物件的類別。這允許 mocks 通過 isinstance() 測試。

  • spec_setspec 的一個更嚴格的變體。如果使用 spec_set,在 mock 上嘗試 set 或取得不在傳遞給 spec_set 的物件上的屬性將會引發 AttributeError

  • side_effect:每當呼叫 Mock 時要呼叫的函式,參見 side_effect 屬性,用於引發例外或動態變更回傳值。該函式使用與 mock 相同的引數呼叫,且除非它回傳 DEFAULT,否則此函式的回傳值將用作回傳值。

    side_effect 也可以是一個例外的類別或實例。在這種情況下,當呼叫 mock 時,該例外將被引發。

    如果 side_effect 是一個可疊代物件,那麼對 mock 的每次呼叫將回傳可疊代物件中的下一個值。

    side_effect 可以透過將其設置為 None 來清除。

  • return_value:當呼叫 mock 時回傳的值。預設情況下,這是一個新的 Mock(在首次存取時建立)。參見 return_value 屬性。

  • unsafe:預設情況下,存取任何以 assertassretasertaseertassrt 開頭的屬性將引發 AttributeError。如果傳遞 unsafe=True,將會允許存取這些屬性。

    在 3.5 版被加入.

  • wraps:被 mock 物件包裝的項目。如果 wraps 不是 None,那麼呼叫 Mock 將透過被包裝的物件(回傳真實結果)。存取 mock 的屬性將會回傳一個 Mock 物件,該物件包裝了被包裝物件的對應屬性(因此嘗試存取不存在的屬性將引發 AttributeError)。

    如果 mock 已經明確設定了 return_value,那麼呼叫並不會被傳遞到被包裝的物件,而是回傳已設定好的 return_value

  • name:如果 mock 有一個名稱,那麼它會被用於 mock 的 repr。這對於除錯很有用。此名稱將被傳播到子 mocks。

Mocks 還可以使用任意的關鍵字引數進行呼叫。這些關鍵字引數將在建立 mock 之後用於設定 mock 的屬性。欲知更多,請參見 configure_mock() 方法。

assert_called()

確認 mock 至少被呼叫一次。

>>> mock = Mock()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called()

在 3.6 版被加入.

assert_called_once()

確認 mock 只被呼叫一次。

>>> mock = Mock()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_once()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_once()
Traceback (most recent call last):
...
AssertionError: Expected 'method' to have been called once. Called 2 times.
Calls: [call(), call()].

在 3.6 版被加入.

assert_called_with(*args, **kwargs)

這個方法是一個便利的方式,用來斷言最後一次呼叫是以特定方式進行的:

>>> mock = Mock()
>>> mock.method(1, 2, 3, test='wow')
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_with(1, 2, 3, test='wow')
assert_called_once_with(*args, **kwargs)

確認 mock 只被呼叫一次,且該次呼叫使用了指定的引數。

>>> mock = Mock(return_value=None)
>>> mock('foo', bar='baz')
>>> mock.assert_called_once_with('foo', bar='baz')
>>> mock('other', bar='values')
>>> mock.assert_called_once_with('other', bar='values')
Traceback (most recent call last):
  ...
AssertionError: Expected 'mock' to be called once. Called 2 times.
Calls: [call('foo', bar='baz'), call('other', bar='values')].
assert_any_call(*args, **kwargs)

斷言 mock 已經被使用指定的引數呼叫。

這個斷言在 mock 曾經被呼叫過時通過,不同於 assert_called_with()assert_called_once_with(),他們針對的是最近的一次的呼叫,而且對於 assert_called_once_with(),最近一次的呼叫還必須也是唯一一次的呼叫。

>>> mock = Mock(return_value=None)
>>> mock(1, 2, arg='thing')
>>> mock('some', 'thing', 'else')
>>> mock.assert_any_call(1, 2, arg='thing')
assert_has_calls(calls, any_order=False)

斷言 mock 已經使用指定的呼叫方式來呼叫。此斷言會檢查 mock_calls 串列中的呼叫。

如果 any_order 為 false,那麼這些呼叫必須按照順序。在指定的呼叫之前或之後可以有額外的呼叫。

如果 any_order 為 true,那麼這些呼叫可以以任何順序出現,但它們必須全部出現在 mock_calls 中。

>>> mock = Mock(return_value=None)
>>> mock(1)
>>> mock(2)
>>> mock(3)
>>> mock(4)
>>> calls = [call(2), call(3)]
>>> mock.assert_has_calls(calls)
>>> calls = [call(4), call(2), call(3)]
>>> mock.assert_has_calls(calls, any_order=True)
assert_not_called()

斷言 mock 從未被呼叫。

>>> m = Mock()
>>> m.hello.assert_not_called()
>>> obj = m.hello()
>>> m.hello.assert_not_called()
Traceback (most recent call last):
  ...
AssertionError: Expected 'hello' to not have been called. Called 1 times.
Calls: [call()].

在 3.5 版被加入.

reset_mock(*, return_value=False, side_effect=False)

reset_mock 方法重置 mock 物件上的所有呼叫屬性:

>>> mock = Mock(return_value=None)
>>> mock('hello')
>>> mock.called
True
>>> mock.reset_mock()
>>> mock.called
False

This can be useful where you want to make a series of assertions that reuse the same object.

return_value parameter when set to True resets return_value:

>>> mock = Mock(return_value=5)
>>> mock('hello')
5
>>> mock.reset_mock(return_value=True)
>>> mock('hello')
<Mock name='mock()' id='...'>

side_effect parameter when set to True resets side_effect:

>>> mock