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: <lambda>() takes exactly 3 arguments (1 given)

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.

在 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.
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.

在 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

在 3.6 版的變更: reset_mock 函式新增了兩個僅限關鍵字引數 (keyword-only arguments)。

這在你想要進行一系列重複使用同一物件的斷言時非常有用。請注意,預設情況下,reset_mock() 不會清除回傳值、side_effect 或使用普通賦值設定的任何子屬性。如果你想要重置 return_valueside_effect,則將相應的參數設置為 True。Child mock 和回傳值 mock(如果有的話)也會被重置。

備註

return_valueside_effect 是僅限關鍵字引數。

mock_add_spec(spec, spec_set=False)

向 mock 增加一個規格 (spec)。spec 可以是一個物件或一個字串串列 (list of strings)。只有在 spec 上的屬性才能作為 mock 的屬性被取得。

如果 spec_set 為 true,那麼只能設定在規格中的屬性。

attach_mock(mock, attribute)

將一個 mock 作為這個 Mock 的屬性附加,取代它的名稱和上代 (parent)。對附加的 mock 的呼叫將被記錄在這個 Mock 的 method_callsmock_calls 屬性中。

configure_mock(**kwargs)

透過關鍵字引數在 mock 上設定屬性。

可以在使用 method(方法)呼叫時,使用標準點記法 (dot notation) 並將字典拆開,為 child mock 設定屬性、回傳值和 side effects:

>>> mock = Mock()
>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> mock.configure_mock(**attrs)
>>> mock.method()
3
>>> mock.other()
Traceback (most recent call last):
  ...
KeyError

同樣的事情可以在 mock 的建構函式呼叫中實現:

>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> mock = Mock(some_attribute='eggs', **attrs)
>>> mock.some_attribute
'eggs'
>>> mock.method()
3
>>> mock.other()
Traceback (most recent call last):
  ...
KeyError

configure_mock() 的存在是為了在 mock 被建立後更容易進行組態設定。

__dir__()

Mock 物件限制了 dir(some_mock) 僅顯示有用的結果。對於具有 spec 的 mock,這包含所有被允許的 mock 屬性。

請參閱 FILTER_DIR 以了解這種過濾行為的作用,以及如何關閉它。

_get_child_mock(**kw)

建立為了得到屬性和回傳值的 child mock。預設情況下,child mock 將與其上代是相同的型別。Mock 的子類別可能會想要置換此行為,以自定義 child mock 的建立方式。

對於不可呼叫的 mock,將使用可呼叫的變體,而不是任何的自定義子類別。

called

一個 boolean(布林),表述 mock 物件是否已經被呼叫:

>>> mock = Mock(return_value=None)
>>> mock.called
False
>>> mock()
>>> mock.called
True
call_count

一個整數,告訴你 mock 物件被呼叫的次數:

>>> mock = Mock(return_value=None)
>>> mock.call_count
0
>>> mock()
>>> mock()
>>> mock.call_count
2
return_value

設定此值以配置呼叫 mock 時回傳的值:

>>> mock = Mock()
>>> mock.return_value = 'fish'
>>> mock()
'fish'

預設的回傳值是一個 mock 物件,你也可以按照正常的方式配置它:

>>> mock = Mock()
>>> mock.return_value.attribute = sentinel.Attribute
>>> mock.return_value()
<Mock name='mock()()' id='...'>
>>> mock.return_value.assert_called_with()

return_value 也可以在建構函式中設定:

>>> mock = Mock(return_value=3)
>>> mock.return_value
3
>>> mock()
3
side_effect

這可以是一個在呼叫 mock 時要呼叫的函式、一個可疊代物件,或者要引發的例外(類別或實例)。

如果你傳遞一個函式,它將被呼叫,其引數與 mock 相同,且除非該函式回傳 DEFAULT 單例 (singleton),否則對 mock 的呼叫將回傳函式回傳的任何值。如果函式回傳 DEFAULT,那麼 mock 將回傳其正常的回傳值(從 return_value 得到)。

如果你傳遞一個可疊代物件,它將被用於檢索一個疊代器,該疊代器必須在每次呼叫時產出 (yield) 一個值。這個值可以是要引發的例外實例,或者是對 mock 呼叫時要回傳的值(處理 DEFAULT 的方式與函式的狀況相同)。

以下是一個引發例外的 mock 的範例(用於測試 API 的例外處理):

>>> mock = Mock()
>>> mock.side_effect = Exception('Boom!')
>>> mock()
Traceback (most recent call last):
  ...
Exception: Boom!

使用 side_effect 回傳一連串值的範例:

>>> mock = Mock()
>>> mock.side_effect = [3, 2, 1]
>>> mock(), mock(), mock()
(3, 2, 1)

使用可被呼叫物件的範例:

>>> mock = Mock(return_value=3)
>>> def side_effect(*args, **kwargs):
...     return DEFAULT
...
>>> mock.side_effect = side_effect
>>> mock()
3

side_effect 可以在建構函式中設定。以下是一個範例,它將 mock 被呼叫時給的值加一並回傳:

>>> side_effect = lambda value: value + 1
>>> mock = Mock(side_effect=side_effect)
>>> mock(3)
4
>>> mock(-8)
-7

side_effect 設定為 None 可以清除它:

>>> m = Mock(side_effect=KeyError, return_value=3)
>>> m()
Traceback (most recent call last):
 ...
KeyError
>>> m.side_effect = None
>>> m()
3
call_args

這會是 None(如果 mock 尚未被呼叫),或是 mock 上次被呼叫時使用的引數。這將以元組的形式呈現:第一個成員 (member),其可以通過 args 屬性訪問,是 mock 被呼叫時傳遞的所有有序引數(或一個空元組)。第二個成員,其可以通過 kwargs 屬性訪問,是所有關鍵字引數(或一個空字典)。

>>> mock = Mock(return_value=None)
>>> print(mock.call_args)
None
>>> mock()
>>> mock.call_args
call()
>>> mock.call_args == ()
True
>>> mock(3, 4)
>>> mock.call_args
call(3, 4)
>>> mock.call_args == ((3, 4),)
True
>>> mock.call_args.args
(3, 4)
>>> mock.call_args.kwargs
{}
>>> mock(3, 4, 5, key='fish', next='w00t!')
>>> mock.call_args
call(3, 4, 5, key='fish', next='w00t!')
>>> mock.call_args.args
(3, 4, 5)
>>> mock.call_args.kwargs
{'key': 'fish', 'next': 'w00t!'}

call_args,以及串列 call_args_listmethod_callsmock_calls 的成員都是 call 物件。這些都是元組,因此可以解包以獲取各個引數並進行更複雜的斷言。參見 calls as tuples

在 3.8 版的變更: 新增 argskwargs 特性。

call_args_list

這是按順序列出所有呼叫 mock 物件的串列(因此串列的長度表示它被呼叫的次數)。在任何呼叫發生之前,它會是一個空的串列。 call 物件可用於方便地建構呼叫的串列,以便與 call_args_list 進行比較。

>>> mock = Mock(return_value=None)
>>> mock()
>>> mock(3, 4)
>>> mock(key='fish', next='w00t!')
>>> mock.call_args_list
[call(), call(3, 4), call(key='fish', next='w00t!')]
>>> expected = [(), ((3, 4),), ({'key': 'fish', 'next': 'w00t!'},)]
>>> mock.call_args_list == expected
True

call_args_list 的成員都是 call 物件。這些物件可以被拆包為元組,以取得各個引數。參見 calls as tuples

method_calls

除了追蹤對自身的呼叫之外,mock 還會追蹤對方法和屬性的呼叫,以及它們(這些方法和屬性)的方法和屬性:

>>> mock = Mock()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.property.method.attribute()
<Mock name='mock.property.method.attribute()' id='...'>
>>> mock.method_calls
[call.method(), call.property.method.attribute()]

method_calls 的成員都是 call 物件。這些物件可以拆包為元組,以取得各個引數。參見 calls as tuples

mock_calls

mock_calls 記錄了 所有 對 mock 物件的呼叫,包含其方法、魔術方法以及回傳值 mock。

>>> mock = MagicMock()
>>> result = mock(1, 2, 3)
>>> mock.first(a=3)
<MagicMock name='mock.first()' id='...'>
>>> mock.second()
<MagicMock name='mock.second()' id='...'>
>>> int(mock)
1
>>> result(1)
<MagicMock name='mock()()' id='...'>
>>> expected = [call(1, 2, 3), call.first(a=3), call.second(),
... call.__int__(), call()(1)]
>>> mock.mock_calls == expected
True

method_calls 的成員都是 call 物件。這些物件可以拆包為元組,以取得各個引數。參見 calls as tuples

備註

mock_calls 記錄的方式意味著在進行巢狀呼叫時,上代 (ancestor) 呼叫的參數不會被記錄,因此在比較時它們將始終相等:

>>> mock = MagicMock()
>>> mock.top(a=3).bottom()
<MagicMock name='mock.top().bottom()' id='...'>
>>> mock.mock_calls
[call.top(a=3), call.top().bottom()]
>>> mock.mock_calls[-1] == call.top(a=-1).bottom()
True
__class__

通常,物件的 __class__ 屬性會回傳它的型別。但對於擁有 spec 的 mock 物件,__class__ 會回傳 spec 的類別。這允許 mock 物件通過對它們所替代或偽裝的物件進行的 isinstance() 測試:

>>> mock = Mock(spec=3)
>>> isinstance(mock, int)
True

__class__ 可以被指定,這允許 mock 通過 isinstance() 檢查,而不需要強制使用 spec:

>>> mock = Mock()
>>> mock.__class__ = dict
>>> isinstance(mock, dict)
True
class unittest.mock.NonCallableMock(spec=None, wraps=None, name=None, spec_set=None, **kwargs)

Mock 的一個不可呼叫版本。建構函式參數的意義與 Mock 相同,其例外為 return_valueside_effect 在不可呼叫的 mock 上無意義。

使用類別或實例作為 specspec_set 的 mock 物件能夠通過 isinstance() 測試:

>>> mock = Mock(spec=SomeClass)
>>> isinstance(mock, SomeClass)
True
>>> mock = Mock(spec_set=SomeClass())
>>> isinstance(mock, SomeClass)
True

Mock 類別支援 mock 魔術方法。細節請參考魔術方法

Mock類別和 patch() 裝飾器於組態時接受任意的關鍵字引數。對於 patch() 裝飾器,這些關鍵字會傳遞給正在建立 mock 的建構函式。這些關鍵字引數用於配置 mock 的屬性:

>>> m = MagicMock(attribute=3, other='fish')
>>> m.attribute
3
>>> m.other
'fish'

Child mock 的回傳值和 side effect 可以使用使用點記法進行設置。由於你無法直接在呼叫中使用帶有點 (.) 的名稱,因此你必須建立一個字典並使用 ** 解包:

>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> mock = Mock(some_attribute='eggs', **attrs)
>>> mock.some_attribute
'eggs'
>>> mock.method()
3
>>> mock.other()
Traceback (most recent call last):
  ...
KeyError

在匹配對 mock 的呼叫時,使用 spec(或 spec_set)建立的可呼叫 mock 將會內省 (introspect) 規格物件的簽名 (signature)。因此,它可以匹配實際呼叫的引數,無論它們是按位置傳遞還是按名稱傳遞:

>>> def f(a, b, c): pass
...
>>> mock = Mock(spec=f)
>>> mock(1, 2, c=3)
<Mock name='mock()' id='140161580456576'>
>>> mock.assert_called_with(1, 2, 3)
>>> mock.assert_called_with(a=1, b=2, c=3)

這適用於 assert_called_with()assert_called_once_with()assert_has_calls()assert_any_call()。在使用 Autospeccing(自動規格) 時,它還適用於 mock 物件的方法呼叫。

在 3.4 版的變更: 對於已經設置了規格(spec)和自動規格(autospec)的 mock 物件,新增簽名內省功能。

class unittest.mock.PropertyMock(*args, **kwargs)

一個理應在類別上當成 property 或其他 descriptor 的 mock。PropertyMock 提供了 __get__()__set__() 方法,因此你可以在它被提取時指定回傳值。

從物件中提取 PropertyMock 實例會不帶任何引數呼叫 mock。設定它則會用設定的值來呼叫 mock:

>>> class Foo:
...     @property
...     def foo(self):
...         return 'something'
...     @foo.setter
...     def foo(self, value):
...         pass
...
>>> with patch('__main__.Foo.foo', new_callable=PropertyMock) as mock_foo:
...     mock_foo.return_value = 'mockity-mock'
...     this_foo = Foo()
...     print(this_foo.foo)
...     this_foo.foo = 6
...
mockity-mock
>>> mock_foo.mock_calls
[call(), call(6)]

由於 mock 屬性的儲存方式,你無法直接將 PropertyMock 附加到 mock 物件。但是你可以將其附加到 mock 型別的物件:

>>> m = MagicMock()
>>> p = PropertyMock(return_value=3)
>>> type(m).foo = p
>>> m.foo
3
>>> p.assert_called_once_with()
class unittest.mock.AsyncMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)

MagicMock 的非同步版本。AsyncMock 物件的表現將被視為非同步函式,並且呼叫的結果是一個可等待物件。

>>> mock = AsyncMock()
>>> asyncio.iscoroutinefunction(mock)
True
>>> inspect.isawaitable(mock())  
True

mock() 的結果是一個非同步函式,在它被等待後將具有 side_effectreturn_value 的結果:

  • 如果 side_effect 是一個函式,非同步函式將回傳該函式的結果,

  • 如果 side_effect 是一個例外,則非同步函式將引發該例外,

  • 如果 side_effect 是一個可疊代物件,非同步函式將回傳可疊代物件的下一個值,但如果結果序列耗盡,將立即引發 StopAsyncIteration

  • 如果 side_effect 沒有被定義,非同步函式將回傳由 return_value 定義的值,因此在預設情況下,非同步函式回傳一個新的 AsyncMock 物件。

MockMagicMockspec 設定為非同步函式將導致在呼叫後回傳一個協程物件。

>>> async def async_func(): pass
...
>>> mock = MagicMock(async_func)
>>> mock
<MagicMock spec='function' id='...'>
>>> mock()  
<coroutine object AsyncMockMixin._mock_call at ...>

MockMagicMockAsyncMockspec 設定為具有同步和非同步函式的類別,會自動檢測同步函式並將其設定為 MagicMock(如果上代 mock 為 AsyncMockMagicMock)或 Mock(如果上代 mock 為 Mock)。所有非同步函式將被設定為 AsyncMock

>>> class ExampleClass:
...     def sync_foo():
...         pass
...     async def async_foo():
...         pass
...
>>> a_mock = AsyncMock(ExampleClass)
>>> a_mock.sync_foo
<MagicMock name='mock.sync_foo' id='...'>
>>> a_mock.async_foo
<AsyncMock name='mock.async_foo' id='...'>
>>> mock = Mock(ExampleClass)
>>> mock.sync_foo
<Mock name='mock.sync_foo' id='...'>
>>> mock.async_foo
<AsyncMock name='mock.async_foo' id='...'>

在 3.8 版新加入.

assert_awaited()

斷言 mock 至少被等待過一次。請注意這與物件是否被呼叫是分開的,await 關鍵字必須被使用:

>>> mock = AsyncMock()
>>> async def main(coroutine_mock):
...     await coroutine_mock
...
>>> coroutine_mock = mock()
>>> mock.called
True
>>> mock.assert_awaited()
Traceback (most recent call last):
...
AssertionError: Expected mock to have been awaited.
>>> asyncio.run(main(coroutine_mock))
>>> mock.assert_awaited()
assert_awaited_once()

斷言 mock 正好被等待了一次。

>>> mock = AsyncMock()
>>> async def main():
...     await mock()
...
>>> asyncio.run(main())
>>> mock.assert_awaited_once()
>>> asyncio.run(main())
>>> mock.method.assert_awaited_once()
Traceback (most recent call last):
...
AssertionError: Expected mock to have been awaited once. Awaited 2 times.
assert_awaited_with(*args, **kwargs)

斷言最後一次等待使用了指定的引數。

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> asyncio.run(main('foo', bar='bar'))
>>> mock.assert_awaited_with('foo', bar='bar')
>>> mock.assert_awaited_with('other')
Traceback (most recent call last):
...
AssertionError: expected call not found.
Expected: mock('other')
Actual: mock('foo', bar='bar')
assert_awaited_once_with(*args, **kwargs)

斷言 mock 只被等待了一次並使用了指定的引數。

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> asyncio.run(main('foo', bar='bar'))
>>> mock.assert_awaited_once_with('foo', bar='bar')
>>> asyncio.run(main('foo', bar='bar'))
>>> mock.assert_awaited_once_with('foo', bar='bar')
Traceback (most recent call last):
...
AssertionError: Expected mock to have been awaited once. Awaited 2 times.
assert_any_await(*args, **kwargs)

斷言 mock 曾經被使用指定的引數等待過。

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> asyncio.run(main('foo', bar='bar'))
>>> asyncio.run(main('hello'))
>>> mock.assert_any_await('foo', bar='bar')
>>> mock.assert_any_await('other')
Traceback (most recent call last):
...
AssertionError: mock('other') await not found
assert_has_awaits(calls, any_order=False)

斷言 mock 已被使用指定的呼叫進行等待。await_args_list 串列將被檢查以確認等待的內容。

如果 any_order 為 false,則等待必須按照順序。指定的等待之前或之後可以有額外的呼叫。

如果 any_order 為 true,則等待可以以任何順序出現,但它們必須全部出現在 await_args_list 中。

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> calls = [call("foo"), call("bar")]
>>> mock.assert_has_awaits(calls)
Traceback (most recent call last):
...
AssertionError: Awaits not found.
Expected: [call('foo'), call('bar')]
Actual: []
>>> asyncio.run(main('foo'))
>>> asyncio.run(main('bar'))
>>> mock.assert_has_awaits(calls)
assert_not_awaited()

斷言 mock 從未被等待。

>>> mock = AsyncMock()
>>> mock.assert_not_awaited()
reset_mock(*args, **kwargs)

參見 Mock.reset_mock()。同時將 await_count 設定為 0,await_args 設定為 None,並清除 await_args_list

await_count

一個整數,用來記錄 mock 物件已被等待的次數。

>>> mock = AsyncMock()
>>> async def main():
...     await mock()
...
>>> asyncio.run(main())
>>> mock.await_count
1
>>> asyncio.run(main())
>>> mock.await_count
2
await_args

這可能是 None(如果 mock 尚未被等待),或者是上次等待 mock 時使用的引數。與 Mock.call_args 的功能相同。

>>> mock = AsyncMock()
>>> async def main(*args):
...     await mock(*args)
...
>>> mock.await_args
>>> asyncio.run(main('foo'))
>>> mock.await_args
call('foo')
>>> asyncio.run(main('bar'))
>>> mock.await_args
call('bar')
await_args_list

這是一個按照順序記錄 mock 物件所有等待的串列(因此串列的長度表示該物件已被等待的次數)。在進行任何等待之前,此串列為空。

>>> mock = AsyncMock()
>>> async def main(*args):
...     await mock(*args)
...
>>> mock.await_args_list
[]
>>> asyncio.run(main('foo'))
>>> mock.await_args_list
[call('foo')]
>>> asyncio.run(main('bar'))
>>> mock.await_args_list
[call('foo'), call('bar')]

呼叫

Mock 物件可被呼叫。呼叫將回傳設定為 return_value 屬性的值。預設的回傳值是一個新的 Mock 物件;它會在第一次存取回傳值時(無論是顯式存取還是透過呼叫 Mock)被建立,但是這個回傳值會被儲存,之後每次都回傳同一個值。

對物件的呼叫會被記錄在如 call_argscall_args_list 等屬性中。

如果 side_effect 被設定,那麼在呼叫被記錄後它才會被呼叫,所以如果 side_effect 引發例外,呼叫仍然會被記錄。

呼叫 mock 時引發例外的最簡單方式是將 side_effect 設定為例外類別或實例:

>>> m = MagicMock(side_effect=IndexError)
>>> m(1, 2, 3)
Traceback (most recent call last):
  ...
IndexError
>>> m.mock_calls
[call(1, 2, 3)]
>>> m.side_effect = KeyError('Bang!')
>>> m('two', 'three', 'four')
Traceback (most recent call last):
  ...
KeyError: 'Bang!'
>>> m.mock_calls
[call(1, 2, 3), call('two', 'three', 'four')]

如果 side_effect 是一個函式,則該函式回傳的東西就是對 mock 的呼叫所回傳的值。side_effect 函式會使用與 mock 相同的引數被呼叫。這讓你可以根據輸入動態地變更呼叫的回傳值:

>>> def side_effect(value):
...     return value + 1
...
>>> m = MagicMock(side_effect=side_effect)
>>> m(1)
2
>>> m(2)
3
>>> m.mock_calls
[call(1), call(2)]

如果你希望 mock 仍然回傳預設的回傳值(一個新的 mock),或者是任何已設定的回傳值,有兩種方法可以實現。從 side_effect 內部回傳 mock.return_value,或回傳 DEFAULT

>>> m = MagicMock()
>>> def side_effect(*args, **kwargs):
...     return m.return_value
...
>>> m.side_effect = side_effect
>>> m.return_value = 3
>>> m()
3
>>> def side_effect(*args, **kwargs):
...     return DEFAULT
...
>>> m.side_effect = side_effect
>>> m()
3

要刪除 side_effect,並恢復預設行為,將 side_effect 設為 None

>>> m = MagicMock(return_value=6)
>>> def side_effect(*args, **kwargs):
...     return 3
...
>>> m.side_effect = side_effect
>>> m()
3
>>> m.side_effect = None
>>> m()
6

side_effect 也可以是任何可疊代的物件。對 mock 的重複呼叫將從可疊代物件中回傳值(直到疊代物件耗盡並引發 StopIteration 為止):

>>> m = MagicMock(side_effect=[1, 2, 3])
>>> m()
1
>>> m()
2
>>> m()
3
>>> m()
Traceback (most recent call last):
  ...
StopIteration

如果可疊代物件中的任何成員是例外,則它們將被引發而不是被回傳:

>>> iterable = (33, ValueError, 66)
>>> m = MagicMock(side_effect=iterable)
>>> m()
33
>>> m()
Traceback (most recent call last):
 ...
ValueError
>>> m()
66

刪除屬性

Mock 物件會在需要時建立屬性。這使得它們可以假裝成任何種類的物件。

你可能希望一個 mock 物件在 hasattr() 呼叫時回傳 False,或者在屬性被提取時引發 AttributeError。你可以通過將物件提供為 mock 的 spec 來實現這一點,但這並不總是那麼好用。

你可以通過刪除屬性來「阻擋」它們。一旦刪除,再次存取該屬性將會引發 AttributeError

>>> mock = MagicMock()
>>> hasattr(mock, 'm')
True
>>> del mock.m
>>> hasattr(mock, 'm')
False
>>> del mock.f
>>> mock.f
Traceback (most recent call last):
    ...
AttributeError: f

Mock 名稱與名稱屬性

由於 "name" 是傳遞給 Mock 建構函式的引數,如果你想讓你的 mock 物件擁有 "name" 屬性,你不能在建立時直接傳遞它。有兩種替代方法。其中一個選擇是使用 configure_mock()

>>> mock = MagicMock()
>>> mock.configure_mock(name='my_name')
>>> mock.name
'my_name'

更簡單的方法是在 mock 建立後直接設定 "name" 屬性:

>>> mock = MagicMock()
>>> mock.name = "foo"

如同屬性一般附加 mock

當你將一個 mock 附加為另一個 mock 的屬性(或作為回傳值),它將成為該 mock 的「子代 (child)」。對子代的呼叫將被記錄在上代的 method_callsmock_calls 屬性中。這對於配置子代並將它們附加到上代,或將 mock 附加到記錄所有對子代的呼叫的上代並允許你對 mock 間的呼叫順序進行斷言非常有用:

>>> parent = MagicMock()
>>> child1 = MagicMock(return_value=None)
>>> child2 = MagicMock(return_value=None)
>>> parent.child1 = child1
>>> parent.child2 = child2
>>> child1(1)
>>> child2(2)
>>> parent.mock_calls
[call.child1(1), call.child2(2)]

如果 mock 有 name 引數,則上述規則會有例外。這使你可以防止「親屬關係 (parenting)」的建立,假設因為某些原因你不希望這種狀況發生。

>>> mock = MagicMock()
>>> not_a_child = MagicMock(name='not-a-child')
>>> mock.attribute = not_a_child
>>> mock.attribute()
<MagicMock name='not-a-child()' id='...'>
>>> mock.mock_calls
[]

patch() 為你建立的 mock 會自動被賦予名稱。若要將具有名稱的 mock 附加到上代,你可以使用 attach_mock() 方法:

>>> thing1 = object()
>>> thing2 = object()
>>> parent = MagicMock()
>>> with patch('__main__.thing1', return_value=None) as child1:
...     with patch('__main__.thing2', return_value=None) as child2:
...         parent.attach_mock(child1, 'child1')
...         parent.attach_mock(child2, 'child2')
...         child1('one')
...         child2('two')
...
>>> parent.mock_calls
[call.child1('one'), call.child2('two')]

Patchers

patch 裝飾器僅用於在裝飾的函式範圍內對物件進行 patch。它們會自動為你處理 patch 的中止,即使有異常被引發也是如此。所有這些函式也可以在 with 陳述式中使用,或者作為類別裝飾器使用。

patch

備註

關鍵是要在正確的命名空間進行 patch。請參閱 where to patch 一節。

unittest.mock.patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

patch() 充當函式裝飾器、類別裝飾器或情境管理器。在函式或 with 陳述式的內部,目標會被 patch 成一個新的物件。當函式或 with 陳述式結束時,patch 就會被解除。

如果 new 被省略,則如果被 patch 的物件是非同步函式,目標會被替換為 AsyncMock,反之則替換為 MagicMock。如果 patch() 做為裝飾器使用且省略了 new,則所建立的 mock 會作為額外的引數傳遞給被裝飾的函式。如果 patch() 作為情境管理器使用,則所建立的 mock 將由情境管理器回傳。

target 應該是以 'package.module.ClassName' 形式出現的字串。target 會被引入並用 new 物件替換指定的物件,因此 target 必須可從你呼叫 patch() 的環境中引入。target 在執行被裝飾的函式時被引入,而不是在裝飾器作用時 (decoration time)。

specspec_set 關鍵字引數會傳遞給 MagicMock,如果 patch 正在為你建立一個。

此外,你還可以傳遞 spec=Truespec_set=True,這將導致 patch 將被 mock 的物件作為 spec/spec_set 物件傳遞。

new_callable 允許你指定一個不同的類別或可呼叫的物件,用於被呼叫並建立 new 物件。預設情況下,對於非同步函式使用 AsyncMock,而對於其他情況則使用 MagicMock

spec 的一種更強大的形式是 autospec。如果你設定 autospec=True,則該 mock 將使用被替換物件的規格來建立。該 mock 的所有屬性也將具有被替換物件的對應屬性的規格。被 mock 的方法和函式將檢查其引數,如果呼叫時引數與規格不符(被使用錯誤的簽名 (signature) 呼叫),將引發 TypeError。對於替換類別的 mock,它們的回傳值(即 'instance')將具有與類別相同的規格。請參閱 create_autospec() 函式和 Autospeccing(自動規格)

你可以用 autospec=some_object 替代 autospec=True,以使用任意物件作為規格,而不是被替換的物件。

預設情況下,patch() 將無法取代不存在的屬性。如果你傳入 create=True,且屬性不存在,則當被 patch 的函式被呼叫時,patch 將為你建立該屬性,並在被 patch 的函式結束後再次刪除它。這對於撰寫針對你的生產程式碼在執行環境建立的屬性的測試時非常有用。此功能預設為關閉,因為這可能會相當危險。開啟這個功能後,你可以對於實際上不存在的 API 撰寫會通過的測試!

備註

在 3.5 版的變更: 如果你正在 patch 模組中的內建函式,那麼你不需要傳遞 create=True,它預設會被加入。

patch 可以做為 TestCase 類別的裝飾器使用。它透過裝飾類別中的每個測試方法來運作。當你的測試方法共享一組常見的 patch 時,這會減少繁冗的代碼。patch() 通過搜尋以 patch.TEST_PREFIX 開頭的方法名來尋找測試。預設情況下會是 'test',這與 unittest 尋找測試的方式相匹配。你可以通過設定 patch.TEST_PREFIX 來指定別的前綴。

透過 with 陳述式,Patch 可以做為情境管理器使用。patch 適用於 with 陳述式之後的縮排區塊。如果你使用 "as",則被 patch 的物件將被綁定到 "as" 後面的名稱;如果 patch() 正在為你建立一個 mock 物件,這會非常有用。

patch() 接受任意的關鍵字引數。如果被 patch 的物件是非同步的,這些將會被傳遞給 AsyncMock,如果是同步的則會傳遞給 MagicMock,或如果指定了 new_callable,則傳遞給它。

patch.dict(...)patch.multiple(...)patch.object(...) 可用於其餘的使用情境。

patch() 作為函式裝飾器,為你建立 mock 並將其傳遞給被裝飾的函式:

>>> @patch('__main__.SomeClass')
... def function(normal_argument, mock_class):
...     print(mock_class is SomeClass)
...
>>> function(None)
True

Patch 一個類別會以 MagicMock 實例取代該類別。如果該類別在被測試的程式碼中實例化,那麼它將是會被使用的 mock 的 return_value

如果該類別被實例化多次,你可以使用 side_effect 來每次回傳一個新的 mock。 或者你可以將 return_value 設定成你想要的任何值。

若要配置被 patch 的類別的實例方法的回傳值,你必須在 return_value 上進行配置。 例如:

>>> class Class:
...     def method(self):
...         pass
...
>>> with patch('__main__.Class') as MockClass:
...     instance = MockClass.return_value
...     instance.method.return_value = 'foo'
...     assert Class() is instance
...     assert Class().method() == 'foo'
...

如果你使用 specspec_setpatch() 正在取代一個類別,那麼被建立的 mock 的回傳值將具有相同的規格。:

>>> Original = Class
>>> patcher = patch('__main__.Class', spec=True)
>>> MockClass = patcher.start()
>>> instance = MockClass()
>>> assert isinstance(instance, Original)
>>> patcher.stop()

當你想要為被建立的 mock 使用一個替代的類別取代預設的 MagicMock 時,new_callable 引數非常有用。例如,如果你想要一個 NonCallableMock 被使用:

>>> thing = object()
>>> with patch('__main__.thing', new_callable=NonCallableMock) as mock_thing:
...     assert thing is mock_thing
...     thing()
...
Traceback (most recent call last):
  ...
TypeError: 'NonCallableMock' object is not callable

另一個用法是用一個 io.StringIO 實例替換一個物件:

>>> from io import StringIO
>>> def foo():
...     print('Something')
...
>>> @patch('sys.stdout', new_callable=StringIO)
... def test(mock_stdout):
...     foo()
...     assert mock_stdout.getvalue() == 'Something\n'
...
>>> test()

patch() 為你建立 mock 時,通常你需要做的第一件事就是配置 mock。其中一些配置可以在對 patch 的呼叫中完成。你傳遞到呼叫中的任何關鍵字都將用於在被建立的 mock 上設定屬性:

>>> patcher = patch('__main__.thing', first='one', second='two')
>>> mock_thing = patcher.start()
>>> mock_thing.first
'one'
>>> mock_thing.second
'two'

除了被建立的 mock 上的屬性外,還可以配置 child mock 的 return_valueside_effect。它們在語法上不能直接作為關鍵字引數傳入,但是以它們作為鍵的字典仍然可以使用 ** 擴充為一個 patch() 呼叫:

>>> config = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> patcher = patch('__main__.thing', **config)
>>> mock_thing = patcher.start()
>>> mock_thing.method()
3
>>> mock_thing.other()
Traceback (most recent call last):
  ...
KeyError

預設情況下,嘗試 patch 模組中不存在的函式(或類別中的方法或屬性)將會失敗,並引發 AttributeError

>>> @patch('sys.non_existing_attribute', 42)
... def test():
...     assert sys.non_existing_attribute == 42
...
>>> test()
Traceback (most recent call last):
  ...
AttributeError: <module 'sys' (built-in)> does not have the attribute 'non_existing_attribute'

但是在對 patch() 的呼叫中增加 create=True 將使前面的範例按照預期運作:

>>> @patch('sys.non_existing_attribute', 42, create=True)
... def test(mock_stdout):
...     assert sys.non_existing_attribute == 42
...
>>> test()

在 3.8 版的變更: 如果目標是一個非同步函式,patch() 現在會回傳一個 AsyncMock

patch.object

patch.object(target, attribute, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

使用一個 mock 物件 patch 一個物件(目標)上的命名成員(屬性)。

patch.object() 可以做為裝飾器、類別裝飾器或情境管理器使用。引數 newspeccreatespec_setautospecnew_callable 與在 patch() 中的引數具有相同的意義。與 patch() 一樣,patch.object() 接受任意關鍵字引數來配置它所建立的 mock 物件。

當作為類別裝飾器使用時,patch.object() 會遵循 patch.TEST_PREFIX 來選擇要包裝的方法。

你可以使用三個引數或兩個引數來呼叫 patch.object()。三個引數的形式接受要被 patch 的物件、屬性名稱和要替換掉屬性的物件。

當使用兩個引數的形式呼叫時,你會省略要替換的物件,一個 mock 會為你建立並將其作為額外的引數傳遞給被裝飾的函式:

>>> @patch.object(SomeClass, 'class_method')
... def test(mock_method):
...     SomeClass.class_method(3)
...     mock_method.assert_called_with(3)
...
>>> test()

speccreatepatch.object() 的其他引數與在 patch() 中的引數具有相同的意義。

patch.dict

patch.dict(in_dict, values=(), clear=False, **kwargs)

Patch 字典或類字典的物件,並在測試後將字典回復到其原本的狀態。

in_dict 可以是一個字典或一個類對映的容器。如果它是一個對映,那麼它至少必須支援獲取、設定、刪除項目以及對鍵的疊代。

in_dict 也可以是指定字典名稱的字串,然後透過 import 來取得該字典。

values 可以是要設定的值的字典。values 也可以是 (key, value) 對 (pairs) 的可疊代物件。

如果 clear 為 true,則在設定新值之前字典將被清除。

也可以使用任意關鍵字引數呼叫 patch.dict() 以在字典中設定值。

在 3.8 版的變更: patch.dict() 現在在做為情境管理器使用時回傳被 patch 的字典。

patch.dict() 可以做為情境管理器、裝飾器或類別裝飾器使用:

>>> foo = {}
>>> @patch.dict(foo, {'newkey': 'newvalue'})
... def test():
...     assert foo == {'newkey': 'newvalue'}
...
>>> test()
>>> assert foo == {}

當作為類別裝飾器使用時,patch.dict() 會遵循 patch.TEST_PREFIX(預設為 'test')來選擇要包裝的方法:

>>> import os
>>> import unittest
>>> from unittest.mock import patch
>>> @patch.dict('os.environ', {'newkey': 'newvalue'})
... class TestSample(unittest.TestCase):
...     def test_sample(self):
...         self.assertEqual(os.environ['newkey'], 'newvalue')

如果你想在測試中使用不同的前綴,你可以透過設定 patch.TEST_PREFIX 來告知 patcher 使用不同的前綴。請參閱 TEST_PREFIX 以得知如何修改前綴的更多內容。

patch.dict() 可用於在字典中新增成員,或單純地讓測試更改字典,並確保在測試結束時將字典回復原狀。

>>> foo = {}
>>> with patch.dict(foo, {'newkey': 'newvalue'}) as patched_foo:
...     assert foo == {'newkey': 'newvalue'}
...     assert patched_foo == {'newkey': 'newvalue'}
...     # You can add, update or delete keys of foo (or patched_foo, it's the same dict)
...     patched_foo['spam'] = 'eggs'
...
>>> assert foo == {}
>>> assert patched_foo == {}
>>> import os
>>> with patch.dict('os.environ', {'newkey': 'newvalue'}):
...     print(os.environ['newkey'])
...
newvalue
>>> assert 'newkey' not in os.environ

可以在 patch.dict() 呼叫中使用關鍵字來設定字典中的值:

>>> mymodule = MagicMock()
>>> mymodule.function.return_value = 'fish'
>>> with patch.dict('sys.modules', mymodule=mymodule):
...     import mymodule
...     mymodule.function('some', 'args')
...
'fish'

patch.dict() 可以與實際上不是字典的類字典物件一起使用。最低限度它們必須支援項目的獲取、設定、刪除以及疊代或隸屬資格檢測。這對應到魔術方法中的 __getitem__()__setitem__()__delitem__() 以及 __iter__()__contains__()

>>> class Container:
...     def __init__(self):
...         self.values = {}
...     def __getitem__(self, name):
...         return self.values[name]
...     def __setitem__(self, name, value):
...         self.values[name] = value
...     def __delitem__(self, name):
...         del self.values[name]
...     def __iter__(self):
...         return iter(self.values)
...
>>> thing = Container()
>>> thing['one'] = 1
>>> with patch.dict(thing, one=2, two=3):
...     assert thing['one'] == 2
...     assert thing['two'] == 3
...
>>> assert thing['one'] == 1
>>> assert list(thing) == ['one']

patch.multiple

patch.multiple(target, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

在一次呼叫中執行多個 patch。它接受被 patch 的物件(作為物件或透過 import 取得物件的字串)和 patch 的關鍵字引數:

with patch.multiple(settings, FIRST_PATCH='one', SECOND_PATCH='two'):
    ...

如果你想要 patch.multiple() 為你建立 mock,請使用 DEFAULT 作為值。在這種情況下,被建立的 mock 會透過關鍵字傳遞到被裝飾的函式中,並且當 patch.multiple() 作為情境管理器時會回傳字典。

patch.multiple() 可以做為裝飾器、類別裝飾器或情境管理器使用。引數 specspec_setcreateautospecnew_callable 與在 patch() 中的引數具有相同的意義。這些引數將應用於由 patch.multiple() 完成的所有 patch。

當作為類別裝飾器使用時,patch.multiple() 遵循 patch.TEST_PREFIX 來選擇要包裝的方法。

如果你想要 patch.multiple() 為你建立 mock,那麼你可以使用 DEFAULT 作為值。如果你使用 patch.multiple() 作為裝飾器,那麼被建立的 mock 將透過關鍵字傳遞到被裝飾的函式中。:

>>> thing = object()
>>> other = object()

>>> @patch.multiple('__main__', thing=DEFAULT, other=DEFAULT)
... def test_function(thing, other):
...     assert isinstance(thing, MagicMock)
...     assert isinstance(other, MagicMock)
...
>>> test_function()

patch.multiple() 可以與其他 patch 裝飾器巢狀使用,但需要將透過關鍵字傳遞的引數放在 patch() 建立的任何標準引數之後

>>> @patch('sys.exit')
... @patch.multiple('__main__', thing=DEFAULT, other=DEFAULT)
... def test_function(mock_exit, other, thing):
...     assert 'other' in repr(other)
...     assert 'thing' in repr(thing)
...     assert 'exit' in repr(mock_exit)
...
>>> test_function()

如果 patch.multiple() 作為情境管理器使用,則情境管理器回傳的值是一個字典,其中被建立的 mock 會按名稱作為其鍵值:

>>> with patch.multiple('__main__', thing=DEFAULT, other=DEFAULT) as values:
...     assert 'other' in repr(values['other'])
...     assert 'thing' in repr(values['thing'])
...     assert values['thing'] is thing
...     assert values['other'] is other
...

patch 方法:啟動與停止

所有的 patcher 都有 start()stop() 方法。這使得在 setUp 方法中進行 patch 或在你想要在沒有巢狀使用裝飾器或 with 陳述式的情況下進行多個 patch 時變得更簡單。

要使用它們,請像平常一樣呼叫 patch()patch.object()patch.dict() ,並保留對回傳的 patcher 物件的參照。之後你就可以呼叫 start() 將 patch 準備就緒,並呼叫 stop() 來取消 patch。

如果你使用 patch() 為你建立 mock,那麼它將透過呼叫 patcher.start 回傳。:

>>> patcher = patch('package.module.ClassName')
>>> from package import module
>>> original = module.ClassName
>>> new_mock = patcher.start()
>>> assert module.ClassName is not original
>>> assert module.ClassName is new_mock
>>> patcher.stop()
>>> assert module.ClassName is original
>>> assert module.ClassName is not new_mock

一個典型的用法是在一個 TestCasesetUp 方法中執行多個 patch:

>>> class MyTest(unittest.TestCase):
...     def setUp(self):
...         self.patcher1 = patch('package.module.Class1')
...         self.patcher2 = patch('package.module.Class2')
...         self.MockClass1 = self.patcher1.start()
...         self.MockClass2 = self.patcher2.start()
...
...     def tearDown(self):
...         self.patcher1.stop()
...         self.patcher2.stop()
...
...     def test_something(self):
...         assert package.module.Class1 is self.MockClass1
...         assert package.module.Class2 is self.MockClass2
...
>>> MyTest('test_something').run()

警示

如果你使用這個技巧,你必須確保透過呼叫 stop 來 "取消" patch。這可能會比你想像的還要複雜一點,因為如果有例外在 setUp 中被引發,則 tearDown 就不會被呼叫。unittest.TestCase.addCleanup() 會讓這稍微簡單一點:

>>> class MyTest(unittest.TestCase):
...     def setUp(self):
...         patcher = patch('package.module.Class')
...         self.MockClass = patcher.start()
...         self.addCleanup(patcher.stop)
...
...     def test_something(self):
...         assert package.module.Class is self.MockClass
...

作為額外的好處,你不再需要保留對 patcher 物件的參照。

也可以使用 patch.stopall() 來停止所有已啟動的 patch。

patch.stopall()

停止所有運作的 patch。只停止以 start 啟動的 patch。

patch 內建函式

你可以 patch 模組內的任何內建函式。以下範例 patch 內建函式 ord()

>>> @patch('__main__.ord')
... def test(mock_ord):
...     mock_ord.return_value = 101
...     print(ord('c'))
...
>>> test()
101

TEST_PREFIX

所有 patcher 都可以作為類別裝飾器使用。以這種方式使用時,它們包裝了類別上的每個測試方法。Patcher 將 'test' 開頭的方法認定為測試方法。這與 unittest.TestLoader 預設尋找測試方法的方式相同。

你可能會想為你的測試使用不同的前綴。你可以透過設定 patch.TEST_PREFIX 來告知 patcher 使用不同的前綴:

>>> patch.TEST_PREFIX = 'foo'
>>> value = 3
>>>
>>> @patch('__main__.value', 'not three')
... class Thing:
...     def foo_one(self):
...         print(value)
...     def foo_two(self):
...         print(value)
...
>>>
>>> Thing().foo_one()
not three
>>> Thing().foo_two()
not three
>>> value
3

巢狀使用 Patch 裝飾器

如果你想執行多個 patch,那麼你可以簡單地堆疊裝飾器。

你可以使用這個模式來堆疊多個 patch 裝飾器:

>>> @patch.object(SomeClass, 'class_method')
... @patch.object(SomeClass, 'static_method')
... def test(mock1, mock2):
...     assert SomeClass.static_method is mock1
...     assert SomeClass.class_method is mock2
...     SomeClass.static_method('foo')
...     SomeClass.class_method('bar')
...     return mock1, mock2
...
>>> mock1, mock2 = test()
>>> mock1.assert_called_once_with('foo')
>>> mock2.assert_called_once_with('bar')

請注意,裝飾器是從底部向上應用的。這是 Python 應用裝飾器的標準方式。被建立的 mock 傳遞到測試函式中的順序與此順序相同。

該 patch 何處

patch() 的工作原理是(暫時)將 name 指向的物件變更為另一個物件。可以有許多 name 指向任何單一物件,因此為了使 patch 起作用,你必須確保你 patch 了被測試系統使用的 name。

基本原則是在物件被查找的位置進行 patch,該位置不一定與其被定義的位置相同。幾個範例將有助於闡明這一點。

想像一下,我們想要測試一個專案,其結構如下:

a.py
    -> Defines SomeClass

b.py
    -> from a import SomeClass
    -> some_function instantiates SomeClass

現在我們想要測試 some_function,但我們想使用 patch() mock SomeClass。問題是,當我們 import 模組 b 時(我們必須這樣做),它會從模組 a import SomeClass。如果我們使用 patch() 來 mock a.SomeClass,那麼它對我們的測試就不會有任何影響;模組 b 已經有了一個真實的 SomeClass 的參照 ,看起來我們的 patch 並沒有任何效果。

關鍵是在使用(或查找它)的地方 patch SomeClass。在這個情況下,some_function 實際上會在我們 import 它的模組 b 中查找 SomeClass。這裡的 patch 應該長得像這樣:

@patch('b.SomeClass')

然而,考慮另一種情況,其中模組 b 並不是使用 from a import SomeClass,而是 import a,然後 some_function 使用 a.SomeClass。這兩種 import 形式都很常見。在這種情況下,我們想要 patch 的類別正在其模組中被查找,因此我們必須 patch a.SomeClass

@patch('a.SomeClass')

Patch 描述器與代理物件 (Proxy Objects)

patchpatch.object 都正確地 patch 和還原描述器:類別方法、靜態方法以及屬性。你應該在 類別 而不是實例上 patch 它們。它們還可以使用代理屬性存取的一些物件,例如 django 設定物件

MagicMock 以及魔術方法支援

Mock 魔術方法

Mock 支援 mock Python 協定方法,其也被稱作 "魔術方法"。這允許 mock 物件替換容器或實作 Python 協定的其他物件。

由於魔術方法被查找的方式與一般的方法 [2] 不同,因此專門實作了此支援方式。這代表著僅有特定的魔術方法被此方式支援。現在已支援清單中已經幾乎包含了所有魔術方法。如果你需要 mock 任何魔術方法而其尚未被支援,請讓我們知道。

你可以透過將你感興趣的方法設定為函式或 mock 實例來 mock 魔術方法。如果你使用函式,那麼它必須self 作為第一個引數 [3]

>>> def __str__(self):
...     return 'fooble'
...
>>> mock = Mock()
>>> mock.__str__ = __str__
>>> str(mock)
'fooble'
>>> mock = Mock()
>>> mock.__str__ = Mock()
>>> mock.__str__.return_value = 'fooble'
>>> str(mock)
'fooble'
>>> mock = Mock()
>>> mock.__iter__ = Mock(return_value=iter([]))
>>> list(mock)
[]

一個用法是在 with 陳述式中 mock 作為情境管理器使用的物件:

>>> mock = Mock()
>>> mock.__enter__ = Mock(return_value='foo')
>>> mock.__exit__ = Mock(return_value=False)
>>> with mock as m:
...     assert m == 'foo'
...
>>> mock.__enter__.assert_called_with()
>>> mock.__exit__.assert_called_with(None, None, None)

對魔術方法的呼叫並不會出現在 method_calls 中,它們會被記錄在 mock_calls 內。

備註

如果你使用spec關鍵字引數來建立一個 mock,則嘗試設定規格中未包含的魔術方法將引發一個 AttributeError

已支援的魔術方法的完整列表是:

  • __hash____sizeof____repr____str__

  • __dir____format____subclasses__

  • __round____floor____trunc____ceil__

  • 比較方法:__lt____gt____le____ge____eq____ne__

  • 容器方法:__getitem____setitem____delitem____contains____len____iter____reversed____missing__

  • 情境管理器:__enter____exit____aenter____aexit__

  • 一元數值方法:__neg____pos____invert__

  • 數值方法(包括右側 (right hand) 和原地 (in-place) 變體):__add____sub____mul____matmul____truediv____floordiv____mod____divmod____lshift____rshift____and____xor____or____pow__

  • 數值轉換方法:__complex____int____float____index__

  • 描述器方法:__get____set____delete__

  • Pickling:__reduce____reduce_ex____getinitargs____getnewargs____getstate____setstate__

  • 檔案系統路徑表示法:__fspath__

  • 非同步疊代方法:__aiter____anext__

在 3.8 版的變更: 新增對於 os.PathLike.__fspath__() 的支援。

在 3.8 版的變更: 新增對於 __aenter____aexit____aiter____anext__ 的支援。

以下方法存在,但「不」被支援,因為它們在被 mock 使用時,會無法動態設定,或可能導致問題:

  • __getattr____setattr____init____new__

  • __prepare____instancecheck____subclasscheck____del__

Magic Mock

MagicMock 有兩個變體:MagicMockNonCallableMagicMock

class unittest.mock.MagicMock(*args, **kw)

MagicMockMock 的子類別,其預設具有大多數魔術方法的實作。你可以使用 MagicMock,而無需自行配置魔術方法。

建構函式參數的意義與 Mock 中的參數相同。

如果你使用 specspec_set 引數,那麼只有規格中存在的魔術方法會被建立。

class unittest.mock.NonCallableMagicMock(*args, **kw)

MagicMock 的不可呼叫版本。

建構函式參數的意義與 MagicMock 中的參數相同,但 return_valueside_effect 除外,它們對不可呼叫的 mock 來說沒有任何意義。

魔術方法是使用 MagicMock 物件設定的,因此你可以配置它們並以一般的方法來使用它們:

>>> mock = MagicMock()
>>> mock[3] = 'fish'
>>> mock.__setitem__.assert_called_with(3, 'fish')
>>> mock.__getitem__.return_value = 'result'
>>> mock[2]
'result'

預設情況下,許多協定方法都需要回傳特定種類的物件。這些方法預先配置了預設回傳值,因此如果你對回傳值不感興趣,則無需執行任何操作即可使用它們。如果你想更改預設值,你仍然可以手動設定回傳值。

方法及其預設值:

  • __lt__NotImplemented

  • __gt__NotImplemented

  • __le__NotImplemented

  • __ge__NotImplemented

  • __int__1

  • __contains__False

  • __len__0

  • __iter__iter([])

  • __exit__False

  • __aexit__False

  • __complex__1j

  • __float__1.0

  • __bool__True

  • __index__1

  • __hash__:mock 的預設雜湊

  • __str__:mock 的預設字串

  • __sizeof__:mock 的預設 sizeof

舉例來說:

>>> mock = MagicMock()
>>> int(mock)
1
>>> len(mock)
0
>>> list(mock)
[]
>>> object() in mock
False

__eq__()__ne__() 這兩個相等的方法是特別的。它們使用 side_effect 屬性對識別性 (identity) 進行預設的相等比較,除非你變更它們的回傳值以回傳其他內容:

>>> MagicMock() == 3
False
>>> MagicMock() != 3
True
>>> mock = MagicMock()
>>> mock.__eq__.return_value = True
>>> mock == 3
True

MagicMock.__iter__() 的回傳值可以是任何可疊代物件,且不需是一個疊代器:

>>> mock = MagicMock()
>>> mock.__iter__.return_value = ['a', 'b', 'c']
>>> list(mock)
['a', 'b', 'c']
>>> list(mock)
['a', 'b', 'c']

如果回傳值一個疊代器,那麼對其進行一次疊代將消耗它,並且後續疊代將產生一個空串列:

>>> mock.__iter__.return_value = iter(['a', 'b', 'c'])
>>> list(mock)
['a', 'b', 'c']
>>> list(mock)
[]

MagicMock 配置了所有支援的魔術方法,除了一些少見和過時的方法。如果你想要,你仍然可以設定這些魔術方法。

MagicMock 中支援但預設未設置的魔術方法包含:

  • __subclasses__

  • __dir__

  • __format__

  • __get____set____delete__

  • __reversed____missing__

  • __reduce____reduce_ex____getinitargs____getnewargs____getstate____setstate__

  • __getformat__

輔助函式

sentinel(哨兵)

unittest.mock.sentinel

哨兵物件提供了一種為你的測試提供獨特物件的便利方式。

當你使用名稱存取屬性時,屬性會根據需要被建立。存取相同的屬性將始終回傳相同的物件。回傳的物件會具有合適的 repr,讓測試失敗的訊息是可閱讀的。

在 3.7 版的變更: 哨兵屬性現在當被複製序列化時會保留其識別性。

在測試時,有時你需要測試特定物件是否作為引數被傳遞給另一個方法或回傳。建立命名的哨兵物件來測試這一點是常見的。sentinel 提供了一種此類建立和測試物件識別性的便利方式。

在這個例子中,我們 monkey patch method 以回傳 sentinel.some_object

>>> real = ProductionClass()
>>> real.method = Mock(name="method")
>>> real.method.return_value = sentinel.some_object
>>> result = real.method()
>>> assert result is sentinel.some_object
>>> result
sentinel.some_object

DEFAULT

unittest.mock.DEFAULT

DEFAULT 物件是一個預先建立的哨兵(實際上是 sentinel.DEFAULT)。它可以被 side_effect 函式使用來表示正常的回傳值應該被使用。

call

unittest.mock.call(*args, **kwargs)

call_argscall_args_listmock_callsmethod_calls 相比,call() 是一個用於進行更簡單的斷言的輔助物件。call() 也可以與 assert_has_calls() 一起使用。

>>> m = MagicMock(return_value=None)
>>> m(1, 2, a='foo', b='bar')
>>> m()
>>> m.call_args_list == [call(1, 2, a='foo', b='bar'), call()]
True
call.call_list()

對於表示多個呼叫的 call 物件,call_list() 回傳所有中間呼叫以及最終呼叫的串列。

call_list 在對「鍊接呼叫 (chained calls)」進行斷言時特別有用。鍊接呼叫是在單行程式碼進行的多次呼叫。這會導致 mock 上的 mock_calls 中出現多個項目。手動建構呼叫序列會相當單調乏味。

call_list() 可以從同一個鍊接呼叫建構呼叫序列:

>>> m = MagicMock()
>>> m(1).method(arg='foo').other('bar')(2.0)
<MagicMock name='mock().method().other()()' id='...'>
>>> kall = call(1).method(arg='foo').other('bar')(2.0)
>>> kall.call_list()
[call(1),
 call().method(arg='foo'),
 call().method().other('bar'),
 call().method().other()(2.0)]
>>> m.mock_calls == kall.call_list()
True

取決於它的建構方式,一個 call 物件會是(位置引數, 關鍵字引數)的元組,或是 (名稱, 位置引數, 關鍵字引數) 的元組。當你自己建構它們時,這並不是那麼有趣,但是 Mock.call_argsMock.call_args_listMock.mock_calls 屬性中的 call 物件可以被內省以取得它們包含的各個引數。

Mock.call_argsMock.call_args_list 中的 call 物件是(位置引數, 關鍵字引數)的二元組,而 Mock.mock_calls 中的 call 物件以及你自己建立的 call 物件是(名稱, 位置引數, 關鍵字引數)的三元組。

你可以利用它們作為元組的特性來提取單個引數,以進行更複雜的內省和斷言。位置引數是一個元組(如果沒有位置引數則為空元組),關鍵字引數是一個字典:

>>> m = MagicMock(return_value=None)
>>> m(1, 2, 3, arg='one', arg2='two')
>>> kall = m.call_args
>>> kall.args
(1, 2, 3)
>>> kall.kwargs
{'arg': 'one', 'arg2': 'two'}
>>> kall.args is kall[0]
True
>>> kall.kwargs is kall[1]
True
>>> m = MagicMock()
>>> m.foo(4, 5, 6, arg='two', arg2='three')
<MagicMock name='mock.foo()' id='...'>
>>> kall = m.mock_calls[0]
>>> name, args, kwargs = kall
>>> name
'foo'
>>> args
(4, 5, 6)
>>> kwargs
{'arg': 'two', 'arg2': 'three'}
>>> name is m.mock_calls[0][0]
True

create_autospec

unittest.mock.create_autospec(spec, spec_set=False, instance=False, **kwargs)

使用另一個物件作為規格建立一個 mock 物件。Mock 上的屬性將使用 spec 物件上的對應屬性作為其規格。

被 mock 的函式或方法將檢查其引數,以確保他們被使用正確的簽名來呼叫。

如果 spec_setTrue,則嘗試設定規格物件上不存在的屬性將引發 AttributeError

如果一個類別作為規格使用,則 mock(該類別的實例)的回傳值將具有相同的規格。你可以透過傳遞 instance=True 來使用一個類別作為一個實例物件的規格。只有當 mock 的實例是可呼叫物件時,回傳的 mock 才會是可呼叫物件。

create_autospec() 也接受任意的關鍵字引數,這些引數會傳遞給已建立的 mock 的建構函式。

請參閱 Autospeccing(自動規格) 以得知如何以 create_autospec() 使用自動規格以及如何在 patch() 中使用 autospec 引數的範例。

在 3.8 版的變更: 如果目標是一個非同步函式,create_autospec() 現在會回傳一個 AsyncMock

ANY

unittest.mock.ANY

有時你可能需要對 mock 的呼叫中的某些引數進行斷言,但你不在意其他的某些引數,或想將它們單獨從 call_args 中取出並進行更加複雜的斷言。

要忽略某些引數,你可以傳入對所有物件來說都相等的物件。那麼無論傳入什麼內容,對 assert_used_with()assert_used_once_with() 的呼叫都會成功。

>>> mock = Mock(return_value=None)
>>> mock('foo', bar=object())
>>> mock.assert_called_once_with('foo', bar=ANY)

ANY 也可以用來與呼叫串列進行比較,例如 mock_calls

>>> m = MagicMock(return_value=None)
>>> m(1)
>>> m(1, 2)
>>> m(object())
>>> m.mock_calls == [call(1), call(1, 2), ANY]
True

ANY 不只能與呼叫物件比較,其也可以在測試斷言中使用:

class TestStringMethods(unittest.TestCase):

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', ANY])

FILTER_DIR

unittest.mock.FILTER_DIR

FILTER_DIR 是一個模組級別的變數,用於控制 mock 物件回應 dir() 的方式。其預設值為 True,它使用以下描述的過濾方式來只顯示有用的成員。如果你不喜歡這個過濾方式,或由於診斷意圖而需要將其關閉,請設定 mock.FILTER_DIR = False

當過濾方式開啟時,dir(some_mock) 僅會顯示有用的屬性,並將包括通常不會顯示的任何動態建立的屬性。如果 mock 是使用 spec(或 autospec)來建立的,那麼源頭的所有屬性都會顯示,即使它們尚未被存取:

>>> dir(Mock())
['assert_any_call',
 'assert_called',
 'assert_called_once',
 'assert_called_once_with',
 'assert_called_with',
 'assert_has_calls',
 'assert_not_called',
 'attach_mock',
 ...
>>> from urllib import request
>>> dir(Mock(spec=request))
['AbstractBasicAuthHandler',
 'AbstractDigestAuthHandler',
 'AbstractHTTPHandler',
 'BaseHandler',
 ...

許多不是很有用的(對 Mock 來說是私有的,而不是被 mock 的東西)底線和雙底線前綴屬性已從在 Mock 上呼叫 dir() 的結果中濾除。如果你不喜歡這種特性,可以透過設定模組級別開關 FILTER_DIR 來將其關閉:

>>> from unittest import mock
>>> mock.FILTER_DIR = False
>>> dir(mock.Mock())
['_NonCallableMock__get_return_value',
 '_NonCallableMock__get_side_effect',
 '_NonCallableMock__return_value_doc',
 '_NonCallableMock__set_return_value',
 '_NonCallableMock__set_side_effect',
 '__call__',
 '__class__',
 ...

或者,你可以只使用 vars(my_mock)(實例成員)和 dir(type(my_mock))(型別成員)來略過過濾,而不考慮 mock.FILTER_DIR

mock_open

unittest.mock.mock_open(mock=None, read_data=None)

用於建立取代 open() 用途的 mock 的輔助函式。它適用於直接呼叫或用作情境管理器的 open()

mock 引數是要配置的 mock 物件。如果其為 None(預設值),那麼就會為你建立一個 MagicMock,其 API 限制在標準檔案處理上可用的方法或屬性。

read_data 是檔案處理方法 read()readline()readlines() 的回傳字串。對這些方法的呼叫將從 read_data 取得資料,直到資料耗盡。對這些方法的 mock 非常單純:每次呼叫 mock 時,read_data 都會倒回到起點。如果你需要對提供給測試程式碼的資料進行更多控制,你會需要自行客製化這個 mock。如果這樣還不夠,PyPI 上的其中一個記憶體內檔案系統 (in-memory filesystem) 套件可以提供用於測試的真實檔案系統。

在 3.4 版的變更: 新增對 readline()readlines() 的支援。read() 的 mock 更改為消耗 read_data 而不是在每次呼叫時回傳它。

在 3.5 版的變更: 現在,每次呼叫 mock 時都會重置 read_data

在 3.8 版的變更: 新增 __iter__() 到實作中,以便使疊代(例如在 for 迴圈中)正確地消耗 read_data

使用 open() 作為情境管理器是確保檔案處理正確關閉的好方式,且這種方式正在變得普遍:

with open('/some/path', 'w') as f:
    f.write('something')

問題是,即使你 mock 了對 open() 的呼叫,它也是作為情境管理器使用的回傳物件(且其 __enter__()__exit__() 已被呼叫)。

使用 MagicMock mock 情境管理器相當常見並且精細,因此輔助函式就非常有用:

>>> m = mock_open()
>>> with patch('__main__.open', m):
...     with open('foo', 'w') as h:
...         h.write('some stuff')
...
>>> m.mock_calls
[call('foo', 'w'),
 call().__enter__(),
 call().write('some stuff'),
 call().__exit__(None, None, None)]
>>> m.assert_called_once_with('foo', 'w')
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')

以及讀取檔案:

>>> with patch('__main__.open', mock_open(read_data='bibble')) as m:
...     with open('foo') as h:
...         result = h.read()
...
>>> m.assert_called_once_with('foo')
>>> assert result == 'bibble'

Autospeccing(自動規格)

自動規格以 mock 現有的 spec 功能作為基礎。它將 mock 的 api 限制為原始物件(規格)的 api,但它是遞迴的(惰性 (lazily) 實現),因此 mock 的屬性僅具有與規格的屬性相同的 api。此外,被 mock 的函式/方法具有與原始的函式/方法相同的呼叫簽名,因此如果它們被不正確地呼叫,就會引發 TypeError

在解釋自動規格如何運作之前,我們先解釋為什麼需要它。

Mock 是一個非常強大且靈活的物件,但是當用於從被測試的系統中 mock out 物件時,它有兩個缺陷。其中一個缺陷是 Mock api 特有的,另一個缺陷是使用 mock 物件時出現的更普遍的問題。

首先是 Mock 特有的問題。Mock 有兩個非常方便的斷言方法:assert_used_with()assert_used_once_with()

>>> mock = Mock(name='Thing', return_value=None)
>>> mock(1, 2, 3)
>>> mock.assert_called_once_with(1, 2, 3)
>>> mock(1, 2, 3)
>>> mock.assert_called_once_with(1, 2, 3)
Traceback (most recent call last):
 ...
AssertionError: Expected 'mock' to be called once. Called 2 times.

因為 mock 會根據需要自動建立屬性,並允許你使用任意引數呼叫它們,所以如果你拼錯了其中一個斷言方法,那麼你的斷言就不見了:

>>> mock = Mock(name='Thing', return_value=None)
>>> mock(1, 2, 3)
>>> mock.assret_called_once_with(4, 5, 6)  # Intentional typo!

由於拼字錯誤,你的測試可能會安靜且錯誤地通過。

第二個問題對於 mock 來說更為普遍。如果你重構某些程式碼、重新命名成員等等,則對任何仍然使用舊 api 但使用 mock 而不是真實物件的程式碼的測試仍然會通過。這意味著即使你的程式碼已經壞了,你的測試也可以全部通過。

謹記這是你需要有整合測試和單元測試的另一個原因。單獨測試所有內容都很好,但如果你不測試你的單元是如何「連接在一起」的,那麼測試還是有機會發現很多錯誤。

mock 已經提供了一個功能來幫助解決這個問題,其稱為 speccing。如果你使用類別或實例作為 mock 的 spec,那麼你在 mock 上只能存取真實類別中存在的屬性:

>>> from urllib import request
>>> mock = Mock(spec=request.Request)
>>> mock.assret_called_with  # Intentional typo!
Traceback (most recent call last):
 ...
AttributeError: Mock object has no attribute 'assret_called_with'

該規格僅適用於 mock 本身,因此在 mock 上的任何方法仍然有相同的問題:

>>> mock.has_data()
<mock.Mock object at 0x...>
>>> mock.has_data.assret_called_with()  # Intentional typo!

自動規格解決了這個問題。你可以將 autospec=True 傳遞給 patch() / patch.object() 或使用 create_autospec() 函式建立帶有規格的 mock。如果你對 patch() 使用 autospec=True 引數,則被取代的物件將作為規格物件使用。因為規格是「惰性」完成的(規格是在 mock 被存取時作為屬性被建立的),所以你可以將它與非常複雜或深度巢狀使用的物件(例如連續引用的模組)一起使用,而不會過於影響性能。

這是一個正在使用的例子:

>>> from urllib import request
>>> patcher = patch('__main__.request', autospec=True)
>>> mock_request = patcher.start()
>>> request is mock_request
True
>>> mock_request.Request
<MagicMock name='request.Request' spec='Request' id='...'>

你可以看到 request.Request 有一個規格。request.Request 在建構函式中接受兩個引數(其中之一是 self)。如果我們錯誤地呼叫它,會發生以下情況:

>>> req = request.Request()
Traceback (most recent call last):
 ...
TypeError: <lambda>() takes at least 2 arguments (1 given)

此規格也適用於實例化的類別(即有規格的 mock 的回傳值):

>>> req = request.Request('foo')
>>> req
<NonCallableMagicMock name='request.Request()' spec='Request' id='...'>

Request 物件不是可呼叫物件,因此實例化我們 mock out 的 request.Request 的回傳值是不可呼叫的 mock。規格到位後,斷言中的任何拼字錯誤都會引發正確的錯誤:

>>> req.add_header('spam', 'eggs')
<MagicMock name='request.Request().add_header()' id='...'>
>>> req.add_header.assret_called_with  # Intentional typo!
Traceback (most recent call last):
 ...
AttributeError: Mock object has no attribute 'assret_called_with'
>>> req.add_header.assert_called_with('spam', 'eggs')

在許多情況下,你只需要將 autospec=True 新增至現有的 patch() 呼叫中,然後就可以防止因拼字錯誤和 api 變更而導致的錯誤。

除了透過 patch() 使用 autospec 之外,還有一個 create_autospec() 用於直接建立有自動規格的 mock:

>>> from urllib import request
>>> mock_request = create_autospec(request)
>>> mock_request.Request('foo', 'bar')
<NonCallableMagicMock name='mock.Request()' spec='Request' id='...'>

然而,這並非完全沒有限制,這就是為什麼它不是預設的行為。為了理解規格物件上有哪些可用屬性,autospec 必須內省(存取屬性)規格。當你遍歷 mock 上的屬性時,原始物件的對應遍歷正在默默發生。如果你的規格物件具有可以觸發程式碼執行的屬性或描述器,那麼你可能無法使用 autospec。換句話說,設計你的物件讓內省是安全的 [4] 會比較好。

一個更嚴重的問題是,在 __init__() 方法中建立實例屬性是常見的,而其根本不存在於類別中。autospec 無法知道任何動態建立的屬性,並將 api 限制為可見的屬性。:

>>> class Something:
...   def __init__(self):
...     self.a = 33
...
>>> with patch('__main__.Something', autospec=True):
...   thing = Something()
...   thing.a
...
Traceback (most recent call last):
  ...
AttributeError: Mock object has no attribute 'a'

有幾種不同的方法可以解決這個問題。最簡單但可能有點煩人的方法是在建立後簡單地在 mock 上設定所需的屬性。因為雖然 autospec 不允許你取得規格中不存在的屬性,但是它不會阻止你設定它們:

>>> with patch('__main__.Something', autospec=True):
...   thing = Something()
...   thing.a = 33
...

specautospec 有一個更激進的版本,它會確實地阻止你設定不存在的屬性。如果你想確保你的程式碼僅能設定有效的屬性,那麼這會很有用,但顯然它也順便阻止了這個特殊情況:

>>> with patch('__main__.Something', autospec=True, spec_set=True):
...   thing = Something()
...   thing.a = 33
...
Traceback (most recent call last):
 ...
AttributeError: Mock object has no attribute 'a'

解決問題的最佳方法可能是新增類別屬性作為在 __init__() 中初始化的實例成員的預設值。請注意,如果你僅在 __init__() 中設定預設屬性,那麼透過類別屬性(當然在實例之間共用)提供它們也會更快。例如:

class Something:
    a = 33

這就帶來了另一個問題。為稍後將成為不同型別的物件的成員提供預設值 None 是相對常見的。None 作為規格是無用的,因為它不允許你存取其上的任何屬性或方法。由於 None 作為規格永遠不會有用,並且可能表示通常屬於其他型別的成員,因此自動規格不會對設定為 None 的成員使用規格。這些會只是普通的 mock(通常是 MagicMocks):

>>> class Something:
...     member = None
...
>>> mock = create_autospec(Something)
>>> mock.member.foo.bar.baz()
<MagicMock name='mock.member.foo.bar.baz()' id='...'>

如果修改正式生產 (production) 類別以新增預設值不符合你的喜好,那麼還有更多選擇。其中之一就是簡單地使用實例作為規格而不是使用類別。另一種是建立一個正式生產類別的子類別,並將預設值新增至子類別中,而不影響正式生產類別。這兩個都需要你使用替代物件作為規格。值得慶幸的是 patch() 支援這一點 - 你可以簡單地將替代物件作為 autospec 引數傳遞:

>>> class Something:
...   def __init__(self):
...     self.a = 33
...
>>> class SomethingForTest(Something):
...   a = 33
...
>>> p = patch('__main__.Something', autospec=SomethingForTest)
>>> mock = p.start()
>>> mock.a
<NonCallableMagicMock name='Something.a' spec='int' id='...'>

密封 mock

unittest.mock.seal(mock)

當存取被密封的 mock 的屬性或其任何已經遞迴 mock 的屬性時,seal 將停用 mock 的自動建立。

如果將具有名稱或規格的 mock 實例指派給屬性,則不會出現在密封鏈中。這表示可藉由固定 mock 物件的一部分來防止密封。:

>>> mock = Mock()
>>> mock.submock.attribute1 = 2
>>> mock.not_submock = mock.Mock(name="sample_name")
>>> seal(mock)
>>> mock.new_attribute  # This will raise AttributeError.
>>> mock.submock.attribute2  # This will raise AttributeError.
>>> mock.not_submock.attribute2  # This won't raise.

在 3.7 版新加入.

side_effect, return_valuewraps 的优先顺序

它们的优先顺序是:

  1. side_effect

  2. return_value

  3. wraps

如果三个均已设置,模拟对象将返回来自 side_effect 的值,完全忽略 return_value 和被包装的对象。 如果设置任意两个,则具有更高优先级的那个将返回值。 无论设置的顺序是哪个在前,优先级顺序将保持不变。

>>> from unittest.mock import Mock
>>> class Order:
...     @staticmethod
...     def get_value():
...         return "third"
...
>>> order_mock = Mock(spec=Order, wraps=Order)
>>> order_mock.get_value.side_effect = ["first"]
>>> order_mock.get_value.return_value = "second"
>>> order_mock.get_value()
'first'

由于 Noneside_effect 的默认值,如果你将其值重新赋为 None,则优先级顺序将在 return_value 和被包装的对象之间进行检查,并忽略 side_effect

>>> order_mock.get_value.side_effect = None
>>> order_mock.get_value()
'second'

如果 side_effect 所返回的值为 DEFAULT,它将被忽略并且优先级顺序将移至后继者来获取要返回的值。

>>> from unittest.mock import DEFAULT
>>> order_mock.get_value.side_effect = [DEFAULT]
>>> order_mock.get_value()
'second'

Mock 包装一个对象时,return_value 的默认值将为 DEFAULT

>>> order_mock = Mock(spec=Order, wraps=Order)
>>> order_mock.return_value
sentinel.DEFAULT
>>> order_mock.get_value.return_value
sentinel.DEFAULT

优先级顺序将忽略该值并且它将移至末尾的后继者即被包装的对象。

由于真正调用的是被包装的对象,创建该模拟对象的实例将返回真正的该类实例。 被包装的对象所需要的任何位置参数都必须被传入。

>>> order_mock_instance = order_mock()
>>> isinstance(order_mock_instance, Order)
True
>>> order_mock_instance.get_value()
'third'
>>> order_mock.get_value.return_value = DEFAULT
>>> order_mock.get_value()
'third'
>>> order_mock.get_value.return_value = "second"
>>> order_mock.get_value()
'second'

但是如果你将其赋值为 None,由于它是一个显式赋值所以不会被忽略。 因此,优先级顺序将不会移至被包装的对象。

>>> order_mock.get_value.return_value = None
>>> order_mock.get_value() is None
True

即使你在初始化模拟对象时立即立即全部设置这三者,优先级顺序仍会保持原样:

>>> order_mock = Mock(spec=Order, wraps=Order,
...                   **{"get_value.side_effect": ["first"],
...                      "get_value.return_value": "second"}
...                   )
...
>>> order_mock.get_value()
'first'
>>> order_mock.get_value.side_effect = None
>>> order_mock.get_value()
'second'
>>> order_mock.get_value.return_value = DEFAULT
>>> order_mock.get_value()
'third'

如果 side_effect 已耗尽,优先级顺序将不会导致从后续者获取值。 而是会引发 StopIteration 异常。

>>> order_mock = Mock(spec=Order, wraps=Order)
>>> order_mock.get_value.side_effect = ["first side effect value",
...                                     "another side effect value"]
>>> order_mock.get_value.return_value = "second"
>>> order_mock.get_value()
'first side effect value'
>>> order_mock.get_value()
'another side effect value'
>>> order_mock.get_value()
Traceback (most recent call last):
 ...
StopIteration