unittest.mock — 模擬物件庫

在 3.3 版本加入。

原始碼: Lib/unittest/mock.py


unittest.mock 是一個用於 Python 測試的庫。它允許你用模擬物件替換被測系統的一部分,並對它們的使用方式進行斷言。

unittest.mock 提供了一個核心 Mock 類,從而無需在整個測試套件中建立大量的存根。執行操作後,你可以對使用了哪些方法/屬性以及呼叫它們的引數進行斷言。你還可以以正常方式指定返回值和設定所需的屬性。

此外,mock 還提供了一個 patch() 裝飾器,用於在測試範圍內修補模組和類級別的屬性,以及用於建立唯一物件的 sentinel。有關如何使用 MockMagicMockpatch() 的示例,請參閱 快速指南

Mock 旨在與 unittest 結合使用,並基於“動作 -> 斷言”模式,而不是許多模擬框架使用的“記錄 -> 回放”模式。

有一個適用於早期 Python 版本的 unittest.mock 的向後移植版本,可在 PyPI 上作為 mock 獲得。

快速指南

MockMagicMock 物件在訪問時建立所有屬性和方法,並存儲它們的使用細節。你可以配置它們,指定返回值或限制可用屬性,然後對它們的使用方式進行斷言

>>> 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 允許你執行副作用,包括在呼叫模擬時引發異常

>>> 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* 引數配置模擬從另一個物件獲取其規範。嘗試訪問模擬上不存在於規範中的屬性或方法將失敗並引發 AttributeError

patch() 裝飾器/上下文管理器使得在被測模組中模擬類或物件變得容易。你指定的物件將在測試期間被模擬(或其他物件)替換,並在測試結束時恢復

>>> 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 裝飾器時,模擬將按照它們應用的順序(Python 裝飾器應用的正常順序)傳遞給被裝飾函式。這意味著從下到上,所以在上面的例子中,`module.ClassName1` 的模擬首先傳入。

使用 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(),用於在特定範圍內設定字典中的值,並在測試結束時將字典恢復到其原始狀態

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

Mock 支援模擬 Python 魔法方法。使用魔法方法的最簡單方法是使用 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'

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

這確保瞭如果你的模擬被錯誤使用,它們將像你的生產程式碼一樣失敗

>>> 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 是一個靈活的模擬物件,旨在取代程式碼中存根和測試替身的使用。模擬物件是可呼叫的,並在你訪問它們時將屬性建立為新的模擬物件[1]。訪問相同的屬性將始終返回相同的模擬物件。模擬物件會記錄你如何使用它們,從而允許你斷言你的程式碼對它們做了什麼。

MagicMockMock 的子類,所有魔術方法都已預先建立並可供使用。還有不可呼叫的變體,當你模擬不可呼叫的物件時很有用: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:這可以是一個字串列表或一個現有物件(類或例項),用作模擬物件的規範。如果你傳入一個物件,則透過對該物件呼叫 dir(排除不支援的魔術屬性和方法)來形成字串列表。訪問此列表中不存在的任何屬性將引發 AttributeError

    如果 spec 是一個物件(而不是字串列表),則 __class__ 返回 spec 物件的類。這允許模擬透過 isinstance() 測試。

  • spec_set:*spec* 的更嚴格變體。如果使用,嘗試在模擬物件上 設定 或獲取不存在於作為 *spec_set* 傳入的物件上的屬性將引發 AttributeError

  • side_effect:每當呼叫 Mock 時都會呼叫的函式。請參閱 side_effect 屬性。對於引發異常或動態更改返回值很有用。該函式使用與模擬相同的引數呼叫,除非它返回 DEFAULT,否則此函式的返回值將用作返回值。

    或者,*side_effect* 可以是異常類或例項。在這種情況下,當呼叫模擬時將引發異常。

    如果 side_effect 是可迭代物件,則每次呼叫模擬都會返回可迭代物件中的下一個值。

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

  • return_value:呼叫模擬時返回的值。預設情況下,這是一個新的 Mock(首次訪問時建立)。請參閱 return_value 屬性。

  • unsafe: 預設情況下,訪問任何名稱以 *assert*、*assret*、*asert*、*aseert* 或 *assrt* 開頭的屬性將引發 AttributeError。傳遞 unsafe=True 將允許訪問這些屬性。

    在 3.5 版本加入。

  • wraps:要包裝的模擬物件項。如果 *wraps* 不是 None,則呼叫 Mock 將透過呼叫傳遞給被包裝物件(返回真實結果)。對模擬的屬性訪問將返回一個包裝被包裝物件相應屬性的 Mock 物件(因此嘗試訪問不存在的屬性將引發 AttributeError)。

    如果模擬物件設定了明確的 *return_value*,則不會將呼叫傳遞給被包裝物件,而是返回 *return_value*。

  • name:如果模擬有一個名稱,它將用於模擬的 repr 中。這對於除錯很有用。名稱會傳播到子模擬。

模擬也可以用任意關鍵字引數呼叫。這些引數將在模擬建立後用於設定模擬的屬性。有關詳細資訊,請參閱 configure_mock() 方法。

assert_called()

斷言模擬物件至少被呼叫一次。

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

在 3.6 版本加入。

assert_called_once()

斷言模擬物件只被呼叫一次。

>>> 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(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)

斷言模擬物件已使用指定的引數呼叫。

如果模擬物件 被呼叫,則斷言透過,這與 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_calls 列表以查詢呼叫。

如果 *any_order* 為假,則呼叫必須是順序的。在指定呼叫之前或之後可以有額外的呼叫。

如果 *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()

斷言模擬物件從未被呼叫。

>>> 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(return_value=None)
>>> mock('hello')
>>> mock.called
True
>>> mock.reset_mock()
>>> mock.called
False

當你想要進行一系列重用同一物件的斷言時,這會很有用。

return_value 引數設定為 True 時,它會重置 return_value

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

side_effect 引數設定為 True 時,它會重置 side_effect

>>> mock = Mock(side_effect=ValueError)
>>> mock('hello')
Traceback (most recent call last):
  ...
ValueError
>>> mock.reset_mock(side_effect=True)
>>> mock('hello')
<Mock name='mock()' id='...'>

請注意,reset_mock() 預設情況下 不會 清除 return_valueside_effect 或你使用正常賦值設定的任何子屬性。

子模擬也會被重置。

3.6 版中已更改: 向 reset_mock 函式添加了兩個僅限關鍵字的引數。

mock_add_spec(spec, spec_set=False)

為模擬新增一個 spec。*spec* 可以是一個物件或一個字串列表。只有 *spec* 上的屬性才能作為模擬的屬性獲取。

如果 *spec_set* 為 true,則只能設定 spec 上的屬性。

attach_mock(mock, attribute)

將模擬作為此模擬的一個屬性附加,替換其名稱和父級。對附加模擬的呼叫將記錄在此模擬的 method_callsmock_calls 屬性中。

configure_mock(**kwargs)

透過關鍵字引數設定模擬物件的屬性。

可以使用標準點表示法和在方法呼叫中解包字典的方式來設定子模擬的屬性、返回值和副作用

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

在模擬的建構函式呼叫中也可以實現同樣的效果

>>> 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() 的存在是為了在建立模擬後更容易進行配置。

__dir__()

Mock 物件將 dir(some_mock) 的結果限制為有用的結果。對於帶有 *spec* 的模擬,這包括所有允許的模擬屬性。

有關此過濾功能以及如何將其關閉的資訊,請參閱 FILTER_DIR

_get_child_mock(**kw)

為屬性和返回值建立子模擬。預設情況下,子模擬將與父模擬具有相同的型別。Mock 的子類可能希望覆蓋此方法以自定義子模擬的建立方式。

對於不可呼叫的模擬,將使用可呼叫變體(而不是任何自定義子類)。

called

一個布林值,表示模擬物件是否已被呼叫

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

一個整數,表示模擬物件被呼叫的次數

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

設定此項以配置呼叫模擬返回的值

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

預設返回值為模擬物件,您可以以常規方式對其進行配置

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

這可以是一個函式,在呼叫模擬時被呼叫;一個可迭代物件;或一個要引發的異常(類或例項)。

如果你傳入一個函式,它將使用與模擬物件相同的引數進行呼叫,並且除非該函式返回 DEFAULT 單例,否則對模擬物件的呼叫將返回該函式返回的任何值。如果該函式返回 DEFAULT,則模擬物件將返回其正常值(來自 return_value)。

如果你傳入一個可迭代物件,它將被用於獲取一個迭代器,該迭代器必須在每次呼叫時產生一個值。這個值可以是一個要引發的異常例項,也可以是一個要從模擬呼叫中返回的值(DEFAULT 處理與函式情況相同)。

一個引發異常的模擬示例(用於測試 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 可以在建構函式中設定。這是一個示例,它將模擬呼叫的值加一併返回它

>>> 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(如果模擬物件尚未被呼叫),要麼是模擬物件上次被呼叫時使用的引數。這將以元組的形式出現:第一個成員(也可以透過 args 屬性訪問)是模擬物件被呼叫時使用的任何位置引數(或空元組),第二個成員(也可以透過 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 物件。它們是元組,因此可以解包以獲取各個引數並進行更復雜的斷言。請參閱 元組形式的呼叫

3.8 版中已更改: 添加了 argskwargs 屬性。

call_args_list

這是一個按順序對模擬物件進行的所有呼叫的列表(因此列表的長度是它被呼叫的次數)。在進行任何呼叫之前,它是一個空列表。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 物件。它們可以解包為元組以獲取各個引數。請參閱 元組形式的呼叫

method_calls

除了跟蹤對自身的呼叫,模擬物件還跟蹤對方法和屬性以及 它們 的方法和屬性的呼叫

>>> 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 物件。它們可以解包為元組以獲取各個引數。請參閱 元組形式的呼叫

mock_calls

mock_calls 記錄了對模擬物件、其方法、魔法方法 以及 返回值模擬的 所有 呼叫。

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

mock_calls 的成員是 call 物件。它們可以解包為元組以獲取各個引數。請參閱 元組形式的呼叫

備註

記錄 mock_calls 的方式意味著,當進行巢狀呼叫時,祖先呼叫的引數不會被記錄,因此將始終比較相等

>>> 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 的模擬物件,__class__ 會返回 spec 類。這使得模擬物件能夠透過它們替換/偽裝的物件進行 isinstance() 測試。

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

__class__ 是可賦值的,這允許模擬透過 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 除外,它們在不可呼叫模擬上沒有意義。

使用類或例項作為 specspec_set 的模擬物件能夠透過 isinstance() 測試。

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

Mock 類支援模擬魔法方法。有關完整詳細資訊,請參閱 魔法方法

模擬類和 patch() 裝飾器都接受任意關鍵字引數進行配置。對於 patch() 裝飾器,關鍵字會傳遞給正在建立的模擬的建構函式。關鍵字引數用於配置模擬的屬性

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

子模擬的返回值和副作用可以透過相同的方式設定,使用點表示法。由於你不能直接在呼叫中使用點名,你必須建立一個字典並使用 ** 解包它。

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

使用 *spec*(或 *spec_set*)建立的可呼叫模擬物件在匹配對模擬物件的呼叫時,將內省規範物件的簽名。因此,無論它們是透過位置引數還是按名稱傳遞,它都可以匹配實際呼叫的引數。

>>> 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()。當 自動規範化 時,它也將適用於模擬物件上的方法呼叫。

3.4 版中已更改: 在規範化和自動規範化的模擬物件上添加了簽名內省。

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

一個模擬物件,旨在用作類上的 property 或其他 描述符PropertyMock 提供 __get__()__set__() 方法,因此您可以在獲取它時指定返回值。

從物件中獲取 PropertyMock 例項會呼叫模擬物件,不帶引數。設定它會呼叫模擬物件,並帶有所設定的值。

>>> 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)]

由於模擬屬性的儲存方式,你無法直接將 PropertyMock 附加到模擬物件。相反,你可以將其附加到模擬型別物件上

>>> m = MagicMock()
>>> p = PropertyMock(return_value=3)
>>> type(m).foo = p
>>> m.foo
3
>>> p.assert_called_once_with()

注意

如果 PropertyMock 引發 AttributeError,它將被解釋為缺少描述符,並且將對父模擬物件呼叫 __getattr__()

>>> m = MagicMock()
>>> no_attribute = PropertyMock(side_effect=AttributeError)
>>> type(m).my_property = no_attribute
>>> m.my_property
<MagicMock name='mock.my_property' id='140165240345424'>

有關詳細資訊,請參閱 __getattr__()

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()
>>> inspect.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(如果父模擬是 AsyncMockMagicMock)或 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()

斷言模擬物件至少被 awaited 一次。請注意,這與物件是否被呼叫是分開的,必須使用 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()

斷言模擬只被 awaited 一次。

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

斷言上次 awaited 是使用指定引數完成的。

>>> 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 await not found.
Expected: mock('other')
Actual: mock('foo', bar='bar')
assert_awaited_once_with(*args, **kwargs)

斷言模擬物件只被 awaited 一次,並且使用了指定的引數。

>>> 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)

斷言模擬曾被使用指定的引數 awaited。

>>> 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)

斷言模擬已使用指定的 await 呼叫。將檢查 await_args_list 列表以查詢 awaits。

如果 *any_order* 為假,則 awaits 必須是順序的。在指定的 awaits 之前或之後可以有額外的呼叫。

如果 *any_order* 為 true,則 awaits 可以是任何順序,但它們都必須出現在 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()

斷言模擬物件從未被 awaited。

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

請參閱 Mock.reset_mock()。還將 await_count 設定為 0,await_args 設定為 None,並清除 await_args_list

await_count

一個整數,用於跟蹤模擬物件已被 awaited 的次數。

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

這要麼是 None(如果模擬物件尚未被 awaited),要麼是模擬物件上次被 awaited 時使用的引數。功能與 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

這是一個按順序對模擬物件進行的所有 awaits 的列表(因此列表的長度是它被 awaited 的次數)。在進行任何 awaits 之前,它是一個空列表。

>>> 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')]
class unittest.mock.ThreadingMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, *, timeout=UNSET, **kwargs)

MagicMock 的一個版本,用於多執行緒測試。ThreadingMock 物件提供了額外的方法來等待呼叫被呼叫,而不是立即斷言。

預設超時由 timeout 引數指定,如果未設定,則由 ThreadingMock.DEFAULT_TIMEOUT 屬性指定,預設為阻塞 (None)。

您可以透過設定 ThreadingMock.DEFAULT_TIMEOUT 來配置全域性預設超時。

wait_until_called(*, timeout=UNSET)

等待直到模擬物件被呼叫。

如果在建立模擬時傳遞了超時時間,或者如果將超時引數傳遞給此函式,則如果呼叫未及時執行,則該函式會引發 AssertionError

>>> mock = ThreadingMock()
>>> thread = threading.Thread(target=mock)
>>> thread.start()
>>> mock.wait_until_called(timeout=1)
>>> thread.join()
wait_until_any_call_with(*args, **kwargs)

等待直到使用指定引數呼叫模擬物件。

如果在建立模擬時傳遞了超時時間,則如果呼叫未及時執行,函式會引發 AssertionError

>>> mock = ThreadingMock()
>>> thread = threading.Thread(target=mock, args=("arg1", "arg2",), kwargs={"arg": "thing"})
>>> thread.start()
>>> mock.wait_until_any_call_with("arg1", "arg2", arg="thing")
>>> thread.join()
DEFAULT_TIMEOUT

建立 ThreadingMock 例項的全域性預設超時時間(秒)。

在 3.13 版本加入。

呼叫

模擬物件是可呼叫的。呼叫將返回設定為 return_value 屬性的值。預設返回值為一個新的 Mock 物件;它在首次訪問返回值時(無論是顯式還是透過呼叫 Mock)建立,但它會被儲存,並且每次都會返回同一個物件。

對物件進行的呼叫將記錄在 call_argscall_args_list 等屬性中。

如果設定了 side_effect,則它將在記錄呼叫後被呼叫,因此即使 side_effect 丟擲異常,呼叫仍會被記錄。

讓模擬物件在被呼叫時引發異常的最簡單方法是將 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 是一個函式,那麼該函式返回的任何值都將是模擬物件呼叫的返回值。該 side_effect 函式使用與模擬物件相同的引數進行呼叫。這允許你根據輸入動態地改變呼叫的返回值。

>>> 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)]

如果你希望模擬物件仍然返回預設返回值(一個新的模擬物件),或任何設定的返回值,那麼有兩種方法可以實現。要麼在 side_effect 內部返回 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 也可以是任何可迭代物件。對模擬物件的重複呼叫將返回可迭代物件中的值(直到可迭代物件耗盡並引發 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

刪除屬性

模擬物件按需建立屬性。這使它們能夠偽裝成任何型別的物件。

你可能希望模擬物件在 hasattr() 呼叫時返回 False,或在獲取屬性時引發 AttributeError。你可以透過為模擬提供一個物件作為 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

模擬名稱和名稱屬性

由於“name”是 Mock 建構函式的一個引數,如果您希望模擬物件具有“name”屬性,則不能在建立時直接傳遞。有兩種替代方法。一種選擇是使用 configure_mock()

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

一個更簡單的選擇是在模擬建立後直接設定“name”屬性

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

將模擬作為屬性附加

當您將模擬作為另一個模擬的屬性(或作為返回值)附加時,它就成為該模擬的“子物件”。對子物件的呼叫會記錄在父物件的 method_callsmock_calls 屬性中。這對於配置子模擬然後將其附加到父物件,或者將模擬附加到記錄所有對子物件呼叫的父物件,並允許您對模擬之間的呼叫順序進行斷言非常有用。

>>> 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 = 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() 為您建立的模擬會自動獲得名稱。要將有名稱的模擬附加到父物件,您可以使用 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')]

補丁程式

補丁裝飾器僅用於在其裝飾的函式範圍內修補物件。它們會自動為您處理取消修補,即使引發異常也是如此。所有這些函式也可以與 with 語句一起使用或作為類裝飾器使用。

補丁

備註

關鍵是在正確的名稱空間中進行修補。請參閱 何處修補 部分。

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

patch() 可用作函式裝飾器、類裝飾器或上下文管理器。在函式體或 with 語句內部,target 會被一個新的物件 new 修補。當函式/with 語句退出時,修補會被撤銷。

如果省略 new,那麼如果被修補的物件是非同步函式,目標將替換為 AsyncMock,否則替換為 MagicMock。如果 patch() 用作裝飾器且省略 new,則建立的模擬物件將作為額外引數傳遞給被裝飾的函式。如果 patch() 用作上下文管理器,則建立的模擬物件將由上下文管理器返回。

target 應該是一個形如 'package.module.ClassName' 的字串。目標會被匯入,然後指定的物件會被 new 物件替換,所以目標必須可以從您呼叫 patch() 的環境中匯入。目標是在裝飾函式執行時匯入的,而不是在裝飾時。

如果補丁為您建立了一個 MagicMock,則 specspec_set 關鍵字引數會傳遞給它。

此外,您可以傳遞 spec=Truespec_set=True,這會導致補丁將要被模擬的物件作為 spec/spec_set 物件傳遞。

new_callable 允許您指定一個不同的類或可呼叫物件,該類或可呼叫物件將被呼叫以建立 new 物件。預設情況下,非同步函式使用 AsyncMock,其餘使用 MagicMock

spec 的一個更強大的形式是 autospec。如果您設定 autospec=True,那麼模擬將根據被替換的物件建立一個 spec。模擬的所有屬性也將具有被替換物件的相應屬性的 spec。被模擬的方法和函式將檢查它們的引數,如果用錯誤的簽名呼叫它們,將引發 TypeError。對於替換類的模擬,它們的返回值(“例項”)將具有與類相同的 spec。參見 create_autospec() 函式和 自動規格化

您可以傳遞 autospec=some_object 而不是 autospec=True,以使用任意物件作為規範而不是被替換的物件。

預設情況下,patch() 將無法替換不存在的屬性。如果您傳入 create=True,並且該屬性不存在,則當被修補的函式被呼叫時,patch 將為您建立該屬性,並在被修補的函式退出後再次刪除它。這對於針對您的生產程式碼在執行時建立的屬性編寫測試非常有用。它預設關閉是因為它可能很危險。如果開啟它,您可以針對實際上不存在的 API 編寫透過的測試!

備註

版本 3.5 中更改: 如果您在模組中修補內建函式,則無需傳遞 create=True,它將預設新增。

補丁可以用作 TestCase 類裝飾器。它透過裝飾類中的每個測試方法來工作。當您的測試方法共享一組共同的補丁時,這減少了樣板程式碼。patch() 透過查詢以 patch.TEST_PREFIX 開頭的方法名稱來查詢測試。預設情況下,這是 'test',與 unittest 查詢測試的方式匹配。您可以透過設定 patch.TEST_PREFIX 來指定替代字首。

補丁可以用作上下文管理器,與 with 語句一起使用。在這裡,補丁應用於 with 語句後的縮排塊。如果您使用“as”,則補丁物件將繫結到“as”後面的名稱;如果 patch() 正在為您建立一個模擬物件,則這非常有用。

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

patch.dict(...)patch.multiple(...)patch.object(...) 可用於替代用例。

patch() 作為函式裝飾器,為您建立模擬物件並將其傳遞給被裝飾的函式

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

修補類會將該類替換為 MagicMock 例項。如果該類在被測試的程式碼中例項化,那麼將使用模擬物件的 return_value

如果一個類被多次例項化,您可以使用 side_effect 來每次返回一個新的模擬物件。或者,您可以將 return_value 設定為您想要的任何值。

要配置修補類上例項方法的返回值,您必須在 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_set 並且 patch() 正在替換一個 ,那麼建立的模擬物件的返回值將具有相同的 spec。

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

new_callable 引數在您想為建立的模擬物件使用預設 MagicMock 以外的替代類時非常有用。例如,如果您想使用 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() 正在為您建立一個模擬物件時,通常您需要做的第一件事是配置該模擬物件。一些配置可以在對 patch 的呼叫中完成。您傳遞給呼叫的任意關鍵字都將用於設定建立的模擬物件上的屬性

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

除了在建立的模擬屬性上設定屬性外,例如 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

預設情況下,嘗試修補模組中不存在的函式(或類中不存在的方法或屬性)將失敗並引發 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)

使用模擬物件修補物件(target)上的命名成員(attribute)。

patch.object() 可以用作裝飾器、類裝飾器或上下文管理器。引數 newspeccreatespec_setautospecnew_callablepatch() 具有相同的含義。與 patch() 類似,patch.object() 接受任意關鍵字引數來配置它建立的模擬物件。

當用作類裝飾器時,patch.object() 遵守 patch.TEST_PREFIX 以選擇要包裝的方法。

您可以呼叫帶有三個引數或兩個引數的 patch.object()。三個引數的形式接受要修補的物件、屬性名稱和替換屬性的物件。

當以兩個引數的形式呼叫時,您省略了替換物件,併為您建立了一個模擬物件,作為額外引數傳遞給被裝飾的函式

>>> @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)

修補一個字典或類似字典的物件,並在測試後將字典恢復到其原始狀態,其中恢復的字典是測試前字典的副本。

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

in_dict 也可以是一個指定字典名稱的字串,然後透過匯入來獲取該字典。

values 可以是一個字典,包含要設定在字典中的值。values 也可以是一個由 (key, value) 對組成的迭代器。

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

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

版本 3.8 中更改: 當用作上下文管理器時,patch.dict() 現在返回修補後的字典。

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 來通知補丁程式。有關如何更改值的更多詳細資訊,請參閱 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)

一次性執行多個補丁。它接受要修補的物件(可以是物件,也可以是透過匯入獲取物件的字串)和補丁的關鍵字引數

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

如果您希望 patch.multiple() 為您建立模擬物件,請使用 DEFAULT 作為值。在這種情況下,建立的模擬物件將透過關鍵字傳遞給被裝飾的函式,並且當 patch.multiple() 用作上下文管理器時,將返回一個字典。

patch.multiple() 可用作裝飾器、類裝飾器或上下文管理器。引數 specspec_setcreateautospecnew_callablepatch() 具有相同的含義。這些引數將應用於 patch.multiple() 執行的所有補丁。

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

如果您希望 patch.multiple() 為您建立模擬物件,那麼您可以使用 DEFAULT 作為值。如果您使用 patch.multiple() 作為裝飾器,那麼建立的模擬物件將透過關鍵字傳遞給被裝飾的函式。

>>> 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() 用作上下文管理器,則由上下文管理器返回的值是一個字典,其中建立的模擬物件以名稱為鍵

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

補丁方法:start 和 stop

所有補丁程式都有 start()stop() 方法。這些方法使在 setUp 方法中或在您想進行多個補丁而無需巢狀裝飾器或 with 語句的情況下,修補變得更簡單。

要使用它們,請像往常一樣呼叫 patch()patch.object()patch.dict(),並保留對返回的 patcher 物件的引用。然後您可以呼叫 start() 來應用補丁,並呼叫 stop() 來撤銷它。

如果您使用 patch() 為您建立模擬物件,那麼它將由呼叫 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 方法中進行多次修補

>>> 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 來確保補丁被“撤銷”。這可能比您想象的要麻煩,因為如果在 setUp 中引發異常,則不會呼叫 tearDownunittest.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.stopall()

停止所有活動的補丁。只停止用 start 啟動的補丁。

修補內建函式

您可以修補模組中的任何內建函式。以下示例修補了內建函式 ord()

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

TEST_PREFIX

所有補丁程式都可以用作類裝飾器。當以這種方式使用時,它們會包裝類中的每個測試方法。補丁程式將以 'test' 開頭的方法識別為測試方法。這與 unittest.TestLoader 預設查詢測試方法的方式相同。

您可能希望為測試使用不同的字首。您可以透過設定 patch.TEST_PREFIX 來通知補丁程式不同的字首。

>>> 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.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 應用裝飾器的標準方式。傳遞給測試函式的建立模擬的順序與此順序匹配。

何處修補

patch() 的工作原理是(臨時)將一個名稱指向的物件替換為另一個物件。可能有很多名稱指向同一個物件,因此為了使修補生效,您必須確保修補被測試系統使用的名稱。

基本原則是,您在物件被查詢的地方進行修補,這不一定與它被定義的地方相同。幾個例子將有助於闡明這一點。

假設我們有一個要測試的專案,其結構如下

a.py
    -> Defines SomeClass

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

現在我們想測試 some_function,但我們想使用 patch() 模擬 SomeClass。問題是當我們匯入模組 b 時,我們必須這樣做,因為它從模組 a 匯入 SomeClass。如果我們使用 patch() 來模擬 a.SomeClass,那麼它將對我們的測試沒有影響;模組 b 已經有一個對真實SomeClass 的引用,看起來我們的修補沒有效果。

關鍵在於在 SomeClass 被使用(或被查詢)的地方對其進行修補。在本例中,some_function 實際上會在模組 b 中查詢 SomeClass,我們已經在此匯入了它。修補應該像這樣

@patch('b.SomeClass')

然而,考慮另一種情況,模組 b 不是 from a import SomeClass,而是 import a,並且 some_function 使用 a.SomeClass。這兩種匯入形式都很常見。在這種情況下,我們想要打補丁的類在模組中被查詢,因此我們必須打補丁 a.SomeClass

@patch('a.SomeClass')

修補描述符和代理物件

patchpatch.object 都能正確修補和恢復描述符:類方法、靜態方法和屬性。您應該在而不是例項上修補這些。它們也適用於某些代理屬性訪問的物件,例如 django settings 物件

MagicMock 和魔術方法支援

模擬魔術方法

Mock 支援模擬 Python 協議方法,也稱為 “魔術方法”。這允許模擬物件替換容器或其他實現 Python 協議的物件。

由於魔術方法與普通方法的查詢方式不同[2],因此對此支援進行了專門實現。這意味著只支援特定的魔術方法。支援列表幾乎包含了所有魔術方法。如果有任何缺失且您需要的,請告訴我們。

您可以透過將感興趣的方法設定為函式或模擬例項來模擬魔術方法。如果您使用的是函式,則它必須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.__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 關鍵字引數來建立模擬,那麼嘗試設定一個不在 spec 中的魔術方法將引發 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__

  • 數字方法(包括右側和就地變體):__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__ 的支援。

以下方法存在但受支援,因為它們要麼被模擬使用,要麼不能動態設定,要麼可能導致問題

  • __getattr__, __setattr__, __init____new__

  • __prepare__, __instancecheck__, __subclasscheck__, __del__

魔術模擬

有兩種 MagicMock 變體:MagicMockNonCallableMagicMock

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

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

建構函式引數的含義與 Mock 相同。

如果您使用 specspec_set 引數,那麼只有存在於 spec 中的魔術方法才會被建立。

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

MagicMock 的不可呼叫版本。

建構函式引數的含義與 MagicMock 相同,但 return_valueside_effect 除外,它們在不可呼叫模擬物件上沒有意義。

魔術方法使用 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__: 模擬物件的預設雜湊值

  • __str__: 模擬物件的預設字串表示

  • __sizeof__: 模擬物件的預設大小

例如:

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

兩個相等方法,__eq__()__ne__(),是特殊的。它們使用 side_effect 屬性執行預設的身份相等比較,除非您更改它們的返回值以返回其他內容

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

sentinel 物件提供了一種方便的方式來為您的測試提供唯一物件。

屬性在您透過名稱訪問它們時按需建立。訪問同一屬性將始終返回同一物件。返回的物件具有合理的 repr,以便測試失敗訊息可讀。

版本 3.7 中更改: sentinel 屬性現在在 複製醃製 時保留其標識。

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

在這個例子中,我們對 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 函式使用,以指示應使用正常返回值。

呼叫

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

call() 是一個輔助物件,用於進行更簡單的斷言,以便與 call_argscall_args_listmock_callsmethod_calls 進行比較。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_list() 返回所有中間呼叫和最終呼叫的列表。

call_list 對於對“鏈式呼叫”進行斷言特別有用。鏈式呼叫是單行程式碼上的多個呼叫。這會在模擬物件的 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 物件以及您自己構造的物件是 (名稱, 位置引數, 關鍵字引數) 的三元組。

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

>>> 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)

使用另一個物件作為規範來建立模擬物件。模擬物件上的屬性將使用 spec 物件上的相應屬性作為其規範。

被模擬的函式或方法將檢查其引數,以確保它們以正確的簽名被呼叫。

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

如果一個類被用作規範,那麼模擬物件的返回值(類的例項)將具有相同的規範。您可以透過傳遞 instance=True 將一個類用作例項物件的規範。只有當模擬物件的例項是可呼叫時,返回的模擬物件才是可呼叫的。

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

有關如何將自動規範與 create_autospec() 以及 patch()autospec 引數一起使用的示例,請參閱 自動規範

3.8 版中已更改: 如果目標是非同步函式,create_autospec() 現在返回 AsyncMock

ANY

unittest.mock.ANY

有時您可能需要對模擬呼叫中的 某些 引數進行斷言,但要麼不關心某些引數,要麼希望將它們從 call_args 中單獨提取出來並進行更復雜的斷言。

要忽略某些引數,您可以傳入與 所有 內容都相等的物件。對 assert_called_with()assert_called_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 是一個模組級變數,它控制模擬物件響應 dir() 的方式。預設值為 True,它使用下面描述的過濾方式,只顯示有用的成員。如果您不喜歡這種過濾,或者出於診斷目的需要將其關閉,則設定 mock.FILTER_DIR = False

開啟過濾後,dir(some_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 呼叫 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)) (型別成員) 來繞過過濾,而無論 FILTER_DIR 如何設定。

mock_open

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

一個輔助函式,用於建立模擬物件以替換 open() 的使用。它適用於直接呼叫 open() 或將其用作上下文管理器的情況。

mock 引數是要配置的模擬物件。如果為 None(預設值),則會為您建立一個 MagicMock,其 API 僅限於標準檔案控制代碼上可用的方法或屬性。

read_data 是一個字串,用於檔案控制代碼的 read()readline()readlines() 方法返回。對這些方法的呼叫將從 read_data 中獲取資料,直到其耗盡。這些方法的模擬非常簡單:每次呼叫 mock 時,read_data 都會重新回到開頭。如果您需要更好地控制提供給測試程式碼的資料,則需要自行自定義此模擬。如果這還不夠,PyPI 上的一些記憶體檔案系統包可以為測試提供逼真的檔案系統。

3.4 版中已更改: 增加了對 readline()readlines() 的支援。read() 的模擬更改為消耗 read_data,而不是每次呼叫都返回它。

3.5 版中已更改: read_data 現在在每次呼叫 mock 時都會重置。

3.8 版中已更改: 添加了 __iter__() 到實現中,以便迭代(例如在 for 迴圈中)正確消耗 read_data

使用 open() 作為上下文管理器是確保檔案控制代碼正確關閉的好方法,並且正變得越來越普遍。

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

問題是,即使您模擬了對 open() 的呼叫,但作為上下文管理器使用的卻是 返回的物件(並且呼叫了 __enter__()__exit__())。

使用 MagicMock 模擬上下文管理器很常見,但也足夠複雜,因此一個輔助函式很有用。

>>> 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'

自動規範

自動規範是基於 mock 現有的 spec 特性。它將模擬的 API 限制為原始物件(規範)的 API,但它是遞迴的(惰性實現),因此模擬的屬性只具有與規範屬性相同的 API。此外,模擬函式/方法具有與原始函式相同的呼叫簽名,因此如果它們被錯誤呼叫,則會引發 TypeError

在我解釋自動規範的工作原理之前,先說明為什麼需要它。

Mock 是一個非常強大和靈活的物件,但它有一個普遍的模擬缺陷。如果您重構部分程式碼,重新命名成員等,那麼針對仍在使用 舊 API 但使用模擬而非真實物件的程式碼的任何測試仍然會透過。這意味著您的所有測試都可能透過,即使您的程式碼已經損壞。

3.5 版中已更改: 在 3.5 之前,帶有“assert”單詞拼寫錯誤的測試會靜默透過,而實際上應該引發錯誤。您仍然可以透過向 Mock 傳遞 unsafe=True 來實現此行為。

請注意,這也是您需要整合測試和單元測試的另一個原因。獨立測試一切都很好,但如果您不測試您的單元如何“連線在一起”,仍然存在許多測試可能已經捕獲的錯誤空間。

unittest.mock 已經提供了一個功能來幫助解決這個問題,稱為規範。如果您使用類或例項作為模擬物件的 spec,那麼您只能訪問模擬物件上存在於真實類中的屬性。

>>> 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.header_items()
<mock.Mock object at 0x...>
>>> mock.header_items.assret_called_with()  # Intentional typo!

自動規範解決了這個問題。您可以將 autospec=True 傳遞給 patch() / patch.object(),或者使用 create_autospec() 函式直接建立一個帶規範的模擬物件。如果您使用 patch()autospec=True 引數,那麼被替換的物件將用作規範物件。由於規範是“惰性”完成的(當訪問模擬物件上的屬性時才建立規範),因此您可以將其用於非常複雜或深度巢狀的物件(例如匯入模組的模組又匯入模組的模組),而不會造成很大的效能損失。

這是一個使用示例

>>> 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)

該規範也適用於例項化類(即規範模擬物件的返回值)。

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

Request 物件不可呼叫,因此例項化我們模擬的 request.Request 的返回值是一個不可呼叫的模擬物件。有了規範,我們斷言中的任何拼寫錯誤都將引發正確的錯誤。

>>> 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')

在許多情況下,您只需在現有的 patch() 呼叫中新增 autospec=True,即可防止由於拼寫錯誤和 API 更改導致的錯誤。

除了透過 patch() 使用 autospec 之外,還有一個 create_autospec() 用於直接建立自動規範的模擬物件。

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

然而,這並非沒有注意事項和限制,這也是它不是預設行為的原因。為了瞭解規範物件上有哪些屬性可用,自動規範必須內省(訪問屬性)規範。當您遍歷模擬物件上的屬性時,原始物件會在底層進行相應的遍歷。如果您的任何規範物件具有可能觸發程式碼執行的屬性或描述符,那麼您可能無法使用自動規範。另一方面,更好地設計您的物件以使內省安全會好得多 [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'

有幾種不同的方法來解決這個問題。最簡單,但不一定是最不令人惱火的方法,就是簡單地在建立後在模擬物件上設定所需的屬性。僅僅因為 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

這引出了另一個問題。通常情況下,對於稍後將是不同型別物件的成員,會提供預設值 NoneNone 作為規範將毫無用處,因為它不允許您訪問其上 任何 屬性或方法。由於 None 永遠 不會作為規範有用,並且可能表示通常是其他型別的成員,因此自動規範不會為設定為 None 的成員使用規範。這些將只是普通的模擬物件(好吧 - MagicMock)。

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

如果修改您的生產類以新增預設值不合您的心意,那麼還有更多選擇。其中之一就是簡單地使用例項作為規範而不是類。另一種方法是建立生產類的子類,並將預設值新增到子類中,而不影響生產類。這兩種方法都要求您使用替代物件作為規範。幸運的是,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='...'>

密封模擬物件

unittest.mock.seal(mock)

Seal 將停用在訪問被密封的模擬物件或其任何已遞迴地成為模擬物件的屬性時自動建立模擬物件。

如果一個帶名稱或規範的模擬例項被分配給一個屬性,它將不被視為密封鏈的一部分。這允許阻止 seal 固定模擬物件的一部分。

>>> 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_effectreturn_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