unittest.mock
— 模擬物件庫¶
在 3.3 版本中新增。
原始碼: Lib/unittest/mock.py
unittest.mock
是一個用於 Python 中測試的庫。它允許您使用模擬物件替換被測試系統的部分,並斷言它們的使用方式。
unittest.mock
提供了一個核心的 Mock
類,無需在整個測試套件中建立大量存根。執行操作後,您可以斷言使用了哪些方法/屬性以及呼叫它們的引數。您還可以按正常方式指定返回值並設定所需的屬性。
此外,mock 提供了一個 patch()
裝飾器,用於處理測試範圍內的模組和類級別屬性的 patch,以及 sentinel
用於建立唯一物件。請參閱快速入門,瞭解如何使用 Mock
、MagicMock
和 patch()
的一些示例。
Mock 旨在與 unittest
一起使用,並且基於“操作 -> 斷言”模式,而不是許多模擬框架使用的“記錄 -> 回放”模式。
有一個適用於早期 Python 版本的 unittest.mock
的反向移植版本,在 PyPI 上作為 mock 提供。
快速入門¶
Mock
和 MagicMock
物件會在您訪問它們時建立所有屬性和方法,並存儲它們的使用方式的詳細資訊。您可以配置它們,指定返回值或限制可用屬性,然後斷言它們的使用方式
>>> 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 物件。這通常很簡單,但要快速瞭解,請閱讀在哪裡進行 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,您可以使用 自動規範化 (auto-speccing)。自動規範化可以透過 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
是一個靈活的模擬物件,旨在替代程式碼中使用的樁和測試替身。Mock 是可呼叫的,並在你訪問它們時建立屬性作為新的 mock [1]。訪問相同的屬性將始終返回相同的 mock。Mock 記錄你如何使用它們,允許你斷言你的程式碼對它們做了什麼。
MagicMock
是 Mock
的子類,所有魔術方法都已預先建立並可以使用。還有一些不可呼叫的變體,當你模擬不可呼叫的物件時很有用:NonCallableMock
和 NonCallableMagicMock
。
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 物件的類。這允許 mock 透過isinstance()
測試。spec_set: spec 的更嚴格的變體。如果使用,嘗試在 mock 上設定或獲取一個不是作為 spec_set 傳遞的物件上的屬性,將會引發
AttributeError
。side_effect: 一個在呼叫 Mock 時呼叫的函式。請參閱
side_effect
屬性。用於引發異常或動態更改返回值。該函式使用與 mock 相同的引數呼叫,除非它返回DEFAULT
,否則此函式的返回值將用作返回值。或者,side_effect 可以是異常類或例項。在這種情況下,當呼叫 mock 時將引發異常。
如果 side_effect 是一個可迭代物件,那麼每次呼叫 mock 將返回來自可迭代物件的下一個值。
可以透過將其設定為
None
來清除 side_effect。return_value: 呼叫 mock 時返回的值。預設情況下,這是一個新的 Mock(在首次訪問時建立)。請參閱
return_value
屬性。unsafe: 預設情況下,訪問名稱以 assert、assret、asert、aseert 或 assrt 開頭的任何屬性都將引發
AttributeError
。傳遞unsafe=True
將允許訪問這些屬性。在 3.5 版本中新增。
wraps: 要被 mock 物件包裝的專案。如果 wraps 不是
None
,則呼叫 Mock 會將呼叫傳遞給被包裝的物件(返回真實結果)。對 mock 的屬性訪問將返回一個 Mock 物件,該物件包裝被包裝物件的相應屬性(因此嘗試訪問不存在的屬性將引發AttributeError
)。如果 mock 設定了顯式的 return_value,則呼叫不會傳遞給被包裝的物件,而是返回 return_value。
name: 如果 mock 有名稱,則它將用於 mock 的 repr。這對於除錯很有用。該名稱會傳播到子 mock。
mock 也可以使用任意關鍵字引數進行呼叫。這些引數將在建立 mock 後用於設定 mock 的屬性。有關詳細資訊,請參閱
configure_mock()
方法。- assert_called()¶
斷言 mock 至少被呼叫一次。
>>> mock = Mock() >>> mock.method() <Mock name='mock.method()' id='...'> >>> mock.method.assert_called()
在 3.6 版本中新增。
- assert_called_once()¶
斷言 mock 只被呼叫一次。
>>> mock = Mock() >>> mock.method() <Mock name='mock.method()' id='...'> >>> mock.method.assert_called_once() >>> mock.method() <Mock name='mock.method()' id='...'> >>> mock.method.assert_called_once() Traceback (most recent call last): ... AssertionError: Expected 'method' to have been called once. Called 2 times. Calls: [call(), call()].
在 3.6 版本中新增。
- assert_called_with(*args, **kwargs)¶
此方法是斷言最後一次呼叫以特定方式進行的便捷方法
>>> mock = Mock() >>> mock.method(1, 2, 3, test='wow') <Mock name='mock.method()' id='...'> >>> mock.method.assert_called_with(1, 2, 3, test='wow')
- assert_called_once_with(*args, **kwargs)¶
斷言 mock 只被呼叫一次,並且該呼叫使用指定的引數。
>>> mock = Mock(return_value=None) >>> mock('foo', bar='baz') >>> mock.assert_called_once_with('foo', bar='baz') >>> mock('other', bar='values') >>> mock.assert_called_once_with('other', bar='values') Traceback (most recent call last): ... AssertionError: Expected 'mock' to be called once. Called 2 times. Calls: [call('foo', bar='baz'), call('other', bar='values')].
- assert_any_call(*args, **kwargs)¶
斷言 mock 已使用指定的引數呼叫。
如果 mock 曾經 被呼叫過,則斷言透過,這與
assert_called_with()
和assert_called_once_with()
不同,後者僅當呼叫是最近一次呼叫時才透過,並且對於assert_called_once_with()
而言,它還必須是唯一一次呼叫。>>> mock = Mock(return_value=None) >>> mock(1, 2, arg='thing') >>> mock('some', 'thing', 'else') >>> mock.assert_any_call(1, 2, arg='thing')
- assert_has_calls(calls, any_order=False)¶
斷言 mock 已使用指定的呼叫進行呼叫。檢查
mock_calls
列表中的呼叫。如果 any_order 為 false,則呼叫必須是順序的。在指定的呼叫之前或之後可以有額外的呼叫。
如果 any_order 為 true,則呼叫可以以任何順序出現,但它們都必須出現在
mock_calls
中。>>> mock = Mock(return_value=None) >>> mock(1) >>> mock(2) >>> mock(3) >>> mock(4) >>> calls = [call(2), call(3)] >>> mock.assert_has_calls(calls) >>> calls = [call(4), call(2), call(3)] >>> mock.assert_has_calls(calls, any_order=True)
- assert_not_called()¶
斷言 mock 從未被呼叫。
>>> m = Mock() >>> m.hello.assert_not_called() >>> obj = m.hello() >>> m.hello.assert_not_called() Traceback (most recent call last): ... AssertionError: Expected 'hello' to not have been called. Called 1 times. Calls: [call()].
在 3.5 版本中新增。
- reset_mock(*, return_value=False, side_effect=False)¶
reset_mock 方法重置 mock 物件上的所有呼叫屬性
>>> mock = Mock(return_value=None) >>> mock('hello') >>> mock.called True >>> mock.reset_mock() >>> mock.called False
在 3.6 版本中更改: 為 reset_mock 函式添加了兩個僅關鍵字引數。
當你想進行一系列重用同一物件的斷言時,這會很有用。請注意,
reset_mock()
預設情況下不會清除return_value
、side_effect
或你使用普通賦值設定的任何子屬性。如果你想重置return_value
或side_effect
,則將相應的引數傳遞為True
。子 mock 和返回值 mock(如果有)也會被重置。注意
return_value 和 side_effect 是僅關鍵字引數。
- mock_add_spec(spec, spec_set=False)¶
向 mock 新增一個 spec。 spec 可以是物件或字串列表。只有 spec 上的屬性才能作為 mock 的屬性獲取。
如果 spec_set 為 true,則只能設定 spec 上的屬性。
- attach_mock(mock, attribute)¶
將一個 mock 作為此 mock 的屬性附加,替換其名稱和父級。對附加 mock 的呼叫將記錄在此 mock 的
method_calls
和mock_calls
屬性中。
- configure_mock(**kwargs)¶
透過關鍵字引數設定 mock 上的屬性。
可以使用標準點表示法和在方法呼叫中解包字典來設定子 mock 上的屬性以及返回值和副作用
>>> 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)¶
為屬性和返回值建立子 mock。 預設情況下,子 mock 將與父級型別相同。 Mock 的子類可能希望覆蓋此方法以自定義建立子 mock 的方式。
對於不可呼叫的 mock,將使用可呼叫變體(而不是任何自定義子類)。
- called¶
一個布林值,表示 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
單例,否則對 mock 的呼叫將返回該函式返回的任何值。 如果該函式返回DEFAULT
,則 mock 將返回其正常值(來自return_value
)。如果您傳入一個可迭代物件,它將用於檢索一個迭代器,該迭代器必須在每次呼叫時產生一個值。 該值可以是引發的異常例項,也可以是從對 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 最後一次呼叫時使用的引數。 這將採用元組的形式:第一個成員,也可以透過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_list
、method_calls
和mock_calls
的成員是call
物件。 這些是元組,因此可以解包以獲取單個引數並進行更復雜的斷言。 請參閱 呼叫作為元組。在 3.8 版本中更改: 添加了
args
和kwargs
屬性。
- 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
物件。 這些可以解包為元組以獲取單個引數。 請參閱 呼叫作為元組。
- 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
物件。這些物件可以解包為元組以獲取各個引數。請參閱 將呼叫作為元組。
- 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
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
的 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_value 和 side_effect 除外,它們在不可呼叫的 mock 上沒有意義。
使用類或例項作為 spec
或 spec_set
的 Mock 物件能夠透過 isinstance()
測試。
>>> mock = Mock(spec=SomeClass)
>>> isinstance(mock, SomeClass)
True
>>> mock = Mock(spec_set=SomeClass())
>>> isinstance(mock, SomeClass)
True
Mock
類支援模擬魔法方法。有關完整詳細資訊,請參閱 魔法方法。
mock 類和 patch()
裝飾器都接受任意關鍵字引數進行配置。對於 patch()
裝飾器,關鍵字將傳遞給正在建立的 mock 的建構函式。關鍵字引數用於配置 mock 的屬性。
>>> m = MagicMock(attribute=3, other='fish')
>>> m.attribute
3
>>> m.other
'fish'
可以使用點號表示法以相同的方式設定子 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
使用 spec(或 spec_set)建立的可呼叫 mock 將在匹配對 mock 的呼叫時檢查規範物件的簽名。因此,無論實際呼叫的引數是按位置傳遞還是按名稱傳遞,它都可以匹配實際呼叫的引數。
>>> 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()
。當 自動規範化 時,它也將應用於 mock 物件的方法呼叫。
在 3.4 版本中更改: 在規範化和自動規範化的 mock 物件上添加了簽名檢查。
- class unittest.mock.PropertyMock(*args, **kwargs)¶
一個旨在用作類上的
property
或其他 描述符 的 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()
注意
如果 PropertyMock
引發 AttributeError
,它將被解釋為缺少描述符,並且將在父 mock 上呼叫 __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() >>> asyncio.iscoroutinefunction(mock) True >>> inspect.isawaitable(mock()) True
mock()
的結果是一個非同步函式,該函式在等待後將具有side_effect
或return_value
的結果。如果
side_effect
是一個函式,則非同步函式將返回該函式的結果。如果
side_effect
是一個異常,則非同步函式將引發該異常。如果
side_effect
是一個可迭代物件,則非同步函式將返回該可迭代物件的下一個值,但是,如果結果序列已用完,則會立即引發StopAsyncIteration
。如果未定義
side_effect
,則非同步函式將返回由return_value
定義的值,因此,預設情況下,非同步函式會返回一個新的AsyncMock
物件。
將
Mock
或MagicMock
的 spec 設定為非同步函式將在呼叫後返回協程物件。>>> async def async_func(): pass ... >>> mock = MagicMock(async_func) >>> mock <MagicMock spec='function' id='...'> >>> mock() <coroutine object AsyncMockMixin._mock_call at ...>
將
Mock
,MagicMock
或AsyncMock
的 spec 設定為一個包含非同步和同步函式的類時,會自動檢測同步函式,並將其設定為MagicMock
(如果父 mock 是AsyncMock
或MagicMock
) 或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 過一次。請注意,這與物件是否被呼叫是分開的,必須使用
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 恰好被 await 過一次。
>>> 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)¶
斷言最後一次 await 是使用指定的引數。
>>> 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)¶
斷言 mock 恰好被 await 過一次且使用了指定的引數。
>>> 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 曾經使用指定的引數被 await 過。
>>> 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 且使用了指定的呼叫。
await_args_list
列表會檢查 await 呼叫。如果 *any_order* 為 false,則 await 呼叫必須是順序的。在指定的 await 呼叫之前或之後可以有額外的呼叫。
如果 *any_order* 為 true,則 await 呼叫可以以任何順序出現,但它們都必須出現在
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 從未被 await 過。
>>> 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 物件被 await 的次數。
>>> mock = AsyncMock() >>> async def main(): ... await mock() ... >>> asyncio.run(main()) >>> mock.await_count 1 >>> asyncio.run(main()) >>> mock.await_count 2
- await_args¶
如果 mock 沒有被 await 過,則為
None
,否則為 mock 最後一次被 await 時的引數。功能與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 物件進行的所有 await 呼叫的列表(因此列表的長度是它被 await 的次數)。在進行任何 await 呼叫之前,它是一個空列表。
>>> 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)¶
等待直到 mock 被呼叫。
如果在建立 mock 時傳遞了超時時間,或者如果將超時引數傳遞給此函式,則如果未及時執行呼叫,該函式將引發
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)¶
等待直到 mock 使用指定的引數被呼叫。
如果在建立模擬物件時傳遞了超時時間,如果呼叫未及時執行,該函式將引發
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
屬性的值。預設返回值是一個新的模擬物件;它在首次訪問返回值時建立(顯式或透過呼叫模擬物件),但它會被儲存,並且每次都返回相同的物件。
對物件進行的呼叫將記錄在諸如 call_args
和 call_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 屬性¶
由於 “name” 是 Mock
建構函式的引數,如果你希望你的模擬物件具有 “name” 屬性,你不能只在建立時將其傳入。有兩種替代方法。一種方法是使用 configure_mock()
。
>>> mock = MagicMock()
>>> mock.configure_mock(name='my_name')
>>> mock.name
'my_name'
一個更簡單的選擇是在建立模擬物件後簡單地設定 “name” 屬性。
>>> mock = MagicMock()
>>> mock.name = "foo"
將模擬物件附加為屬性¶
當你將一個模擬物件附加為另一個模擬物件的屬性(或作為返回值)時,它會成為該模擬物件的 “子級”。對子級的呼叫會記錄在父級的 method_calls
和 mock_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 語句中或作為類裝飾器使用。
patch¶
注意
關鍵是在正確的名稱空間中進行修補。請參閱 修補位置 部分。
- 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'
。 target 會被匯入,並且指定的物件會被 new 物件替換,因此 target 必須可以從你呼叫patch()
的環境中匯入。目標是在執行被裝飾的函式時匯入的,而不是在裝飾時匯入的。如果 patch 為你建立
MagicMock
,則 spec 和 spec_set 關鍵字引數將傳遞給它。此外,你可以傳遞
spec=True
或spec_set=True
,這會導致 patch 將被模擬的物件作為 spec/spec_set 物件傳入。new_callable 允許你指定一個不同的類或可呼叫物件,該物件將被呼叫以建立新的物件。預設情況下,非同步函式使用
AsyncMock
,其餘情況使用MagicMock
。spec 的更強大形式是 autospec。 如果你設定
autospec=True
,則將使用被替換物件的 spec 建立 mock。 mock 的所有屬性也將具有被替換物件相應屬性的 spec。 被 mock 的方法和函式將檢查其引數,如果使用錯誤的簽名呼叫它們,則會引發TypeError
。對於替換類的 mock,它們的返回值(“例項”)將具有與類相同的 spec。 請參閱create_autospec()
函式和 自動 spec。除了使用
autospec=True
,你還可以傳遞autospec=some_object
來使用任意物件作為 spec,而不是被替換的物件。預設情況下,
patch()
將無法替換不存在的屬性。如果你傳入create=True
,並且該屬性不存在,則 patch 將在呼叫被 patch 的函式時為你建立該屬性,並在被 patch 的函式退出後再次刪除它。 這對於針對你的生產程式碼在執行時建立的屬性編寫測試非常有用。預設情況下它是關閉的,因為它可能很危險。 啟用它後,你可以針對實際上不存在的 API 編寫透過的測試!注意
在 3.5 版本中變更: 如果你正在 patch 模組中的內建函式,則不需要傳遞
create=True
,它將預設新增。Patch 可以用作
TestCase
類裝飾器。 它透過裝飾類中的每個測試方法來工作。 當你的測試方法共享一組常見的 patching 時,這可以減少樣板程式碼。patch()
透過查詢以patch.TEST_PREFIX
開頭的方法名稱來查詢測試。預設情況下,這是'test'
,這與unittest
查詢測試的方式相匹配。 你可以透過設定patch.TEST_PREFIX
來指定替代字首。Patch 可以用作上下文管理器,使用 with 語句。 在這裡,patching 應用於 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'
...
如果你使用 spec 或 spec_set 並且 patch()
正在替換一個類,那麼建立的 mock 的返回值將具有相同的 spec。
>>> Original = Class
>>> patcher = patch('__main__.Class', spec=True)
>>> MockClass = patcher.start()
>>> instance = MockClass()
>>> assert isinstance(instance, Original)
>>> patcher.stop()
當你想使用替代類來代替預設的 MagicMock
來建立 mock 時,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 上的屬性外,還可以配置子 mock 的屬性,例如 return_value
和 side_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()
patch.object¶
- patch.object(target, attribute, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)¶
使用 mock 物件 patch 物件 (target) 上指定的成員 (attribute)。
patch.object()
可以用作裝飾器、類裝飾器或上下文管理器。引數 new、 spec、 create、 spec_set、 autospec 和 new_callable 的含義與patch()
相同。與patch()
一樣,patch.object()
接受任意關鍵字引數來配置它建立的 mock 物件。當用作類裝飾器時,
patch.object()
會遵循patch.TEST_PREFIX
來選擇要包裝的方法。
你可以使用三個引數或兩個引數呼叫 patch.object()
。三個引數的形式接受要被修補的物件、屬性名和用於替換該屬性的物件。
當使用兩個引數的形式呼叫時,你會省略替換物件,併為你建立一個 mock 物件,並將其作為額外的引數傳遞給被裝飾的函式。
>>> @patch.object(SomeClass, 'class_method')
... def test(mock_method):
... SomeClass.class_method(3)
... mock_method.assert_called_with(3)
...
>>> test()
spec、create 和 patch.object()
的其他引數的含義與它們在 patch()
中的含義相同。
patch.dict¶
- patch.dict(in_dict, values=(), clear=False, **kwargs)¶
修補一個字典或類似字典的物件,並在測試後將字典恢復到其原始狀態。
in_dict 可以是一個字典或類似對映的容器。如果它是一個對映,那麼它至少必須支援獲取、設定和刪除項,以及迭代鍵。
in_dict 也可以是一個字串,指定字典的名稱,然後透過匯入來獲取它。
values 可以是一個要設定在字典中的值的字典。values 也可以是
(key, value)
對的可迭代物件。如果 clear 為 true,則在設定新值之前將清除字典。
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()
為你建立 mock,請使用DEFAULT
作為值。在這種情況下,建立的 mock 透過關鍵字傳遞給被裝飾的函式,並且當patch.multiple()
用作上下文管理器時,會返回一個字典。patch.multiple()
可以用作裝飾器、類裝飾器或上下文管理器。引數 spec、spec_set、create、autospec 和 new_callable 的含義與patch()
相同。這些引數將應用於patch.multiple()
完成的所有修補。當用作類裝飾器時,
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 方法:start 和 stop¶
所有的補丁器都有 start()
和 stop()
方法。這使得在 setUp
方法中或在您想執行多個補丁而無需巢狀裝飾器或 with 語句時進行補丁操作更加簡單。
要使用它們,請像往常一樣呼叫 patch()
,patch.object()
或 patch.dict()
,並保留對返回的 patcher
物件的引用。然後,您可以呼叫 start()
來應用補丁,並呼叫 stop()
來撤銷它。
如果您使用 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
這種方法的典型用例可能是在 TestCase
的 setUp
方法中執行多個補丁。
>>> 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
中引發異常,則不會呼叫 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.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 應用裝飾器的標準方式。傳遞到您的測試函式中的建立的 mock 的順序與此順序匹配。
在哪裡修補¶
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')
修補描述符和代理物件¶
patch 和 patch.object 都可以正確地修補和恢復描述符:類方法,靜態方法和屬性。您應該在類而不是例項上修補它們。它們還可以與某些代理屬性訪問的物件一起使用,例如 django 設定物件。
MagicMock 和魔術方法支援¶
模擬魔術方法¶
Mock
支援模擬 Python 協議方法,也稱為 “魔術方法”。這使得 mock 物件可以替換容器或其他實現 Python 協議的物件。
由於魔術方法與普通方法的查詢方式不同 [2],因此已專門實現了此支援。這意味著僅支援特定的魔術方法。支援的列表包括幾乎所有魔術方法。如果您需要任何缺少的方法,請告知我們。
您可以透過將您感興趣的方法設定為函式或 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.__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,則嘗試設定 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__
的支援。
以下方法存在,但不被支援,因為它們要麼被 mock 使用,要麼不能動態設定,要麼會導致問題
__getattr__
、__setattr__
、__init__
和__new__
__prepare__
、__instancecheck__
、__subclasscheck__
、__del__
Magic Mock¶
有兩種 MagicMock
變體:MagicMock
和 NonCallableMagicMock
。
- class unittest.mock.MagicMock(*args, **kw)¶
MagicMock
是Mock
的子類,預設實現了大多數魔法方法。 你可以使用MagicMock
,而無需自己配置魔法方法。建構函式引數的含義與
Mock
相同。如果使用 spec 或 spec_set 引數,則只會建立 spec 中存在的魔法方法。
- class unittest.mock.NonCallableMagicMock(*args, **kw)¶
MagicMock
的不可呼叫版本。建構函式引數的含義與
MagicMock
相同,但 return_value 和 side_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 的預設大小
例如
>>> 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__
魔法方法應該在類而不是例項上查詢。 不同版本的 Python 在應用此規則時存在不一致之處。 受支援的協議方法應適用於所有受支援的 Python 版本。
該函式基本上被連線到類,但是每個 Mock
例項彼此隔離。
助手¶
sentinel¶
- unittest.mock.sentinel¶
sentinel
物件提供了一種方便的方式來為您的測試提供唯一的物件。當您透過名稱訪問屬性時,會按需建立屬性。訪問同一個屬性將始終返回同一個物件。返回的物件具有合理的 repr,以便測試失敗訊息可讀。
有時在測試時,您需要測試是否將特定物件作為引數傳遞給另一個方法,或者是否被返回。建立命名的標記物件來測試這種情況是很常見的。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
函式使用,以指示應使用正常的返回值。
call¶
- unittest.mock.call(*args, **kwargs)¶
call()
是一個輔助物件,用於進行更簡單的斷言,以便與call_args
、call_args_list
、mock_calls
和method_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 上的 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
物件是 (位置引數, 關鍵字引數) 或 (名稱, 位置引數, 關鍵字引數) 的元組,具體取決於其構造方式。當您自己構造它們時,這並不特別有趣,但是 call
物件位於 Mock.call_args
、 Mock.call_args_list
和 Mock.mock_calls
屬性中,可以進行內省以獲取它們包含的各個引數。
Mock.call_args
和 Mock.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)¶
使用另一個物件作為規範來建立 mock 物件。mock 上的屬性將使用 spec 物件上的相應屬性作為其規範。
將被 mock 的函式或方法將檢查它們的引數,以確保使用正確的簽名呼叫它們。
如果 spec_set 為
True
,則嘗試設定規範物件上不存在的屬性將引發AttributeError
。如果使用類作為規範,則 mock 的返回值(該類的例項)將具有相同的規範。您可以透過傳遞
instance=True
來使用類作為例項物件的規範。僅當 mock 的例項可呼叫時,返回的 mock 才是可呼叫的。create_autospec()
也接受傳遞給所建立 mock 的建構函式的任意關鍵字引數。
請參閱 自動規範,瞭解如何將自動規範與 create_autospec()
和 patch()
的 autospec 引數一起使用。
在 3.8 版本中變更: 如果目標是非同步函式,create_autospec()
現在返回 AsyncMock
。
ANY¶
- unittest.mock.ANY¶
有時,您可能需要對 mock 呼叫的某些引數進行斷言,但是要麼不關心某些引數,要麼想將它們單獨從 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
是一個模組級變數,用於控制 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))
(型別成員)來繞過過濾,而無需考慮 FILTER_DIR
。
mock_open¶
- unittest.mock.mock_open(mock=None, read_data=None)¶
一個輔助函式,用於建立一個 mock 來替換
open()
的使用。它適用於直接呼叫open()
或用作上下文管理器的情況。mock 引數是要配置的 mock 物件。如果為
None
(預設值),則會為你建立一個MagicMock
,其 API 限制為標準檔案控制代碼上可用的方法或屬性。read_data 是一個字串,用於檔案控制代碼的
read()
、readline()
和readlines()
方法的返回值。對這些方法的呼叫將從 read_data 中獲取資料,直到資料耗盡。這些方法的 mock 非常簡單:每次呼叫 mock 時,read_data 都會倒回到開頭。如果你需要更多地控制你提供給被測試程式碼的資料,你需要自己自定義這個 mock。當這還不夠時,PyPI 上的一些記憶體檔案系統包可以為測試提供一個真實的檔案系統。在 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 限制為原始物件(spec)的 API,但它是遞迴的(延遲實現),以便 mock 的屬性只具有與 spec 的屬性相同的 API。此外,mock 的函式/方法具有與原始函式/方法相同的呼叫簽名,因此如果呼叫不正確,它們會引發 TypeError
。
在解釋自動指定如何工作之前,這裡說明為什麼需要它。
Mock
是一個非常強大且靈活的物件,但它存在一個 mock 通有的缺陷。如果你重構了一些程式碼,重新命名了成員等等,任何仍在使用舊 API 但使用 mock 而不是真實物件的程式碼的測試仍然會透過。這意味著即使你的程式碼已損壞,你的測試仍然可以透過。
在 3.5 版本中更改: 在 3.5 之前,當測試應該引發錯誤時,如果單詞 assert 中存在拼寫錯誤,測試會靜默透過。你仍然可以透過將 unsafe=True
傳遞給 Mock 來實現此行為。
請注意,這是你需要整合測試以及單元測試的另一個原因。單獨測試所有內容都很好,但是如果你不測試你的單元是如何“連線在一起的”,仍然有很大的空間可能存在測試可能捕獲到的錯誤。
unittest.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'
spec 僅適用於 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()
函式建立具有 spec 的 mock。如果你使用 patch()
的 autospec=True
引數,那麼被替換的物件將用作 spec 物件。由於指定是“延遲”完成的(當訪問 mock 上的屬性時才建立 spec),因此你可以將其與非常複雜或深度巢狀的物件(例如匯入模組的模組匯入的模組)一起使用,而不會產生很大的效能損失。
這是一個使用示例
>>> 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
有一個 spec。request.Request
在建構函式中接受兩個引數(其中一個是self)。如果我們嘗試不正確地呼叫它,會發生什麼:
>>> req = request.Request()
Traceback (most recent call last):
...
TypeError: <lambda>() takes at least 2 arguments (1 given)
spec 也適用於例項化的類(即,指定的 mock 的返回值)
>>> req = request.Request('foo')
>>> req
<NonCallableMagicMock name='request.Request()' spec='Request' id='...'>
Request
物件不可呼叫,因此例項化我們 mock 掉的 request.Request
的返回值是不可呼叫的 mock。有了 spec,我們斷言中的任何拼寫錯誤都會引發正確的錯誤
>>> 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='...'>
然而,這並非沒有警告和限制,這就是為什麼它不是預設行為的原因。為了知道 spec 物件上可用的屬性,autospec 必須自省(訪問屬性)spec。當你遍歷 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 不允許你獲取 spec 上不存在的屬性,但這並不會阻止你設定它們
>>> with patch('__main__.Something', autospec=True):
... thing = Something()
... thing.a = 33
...
spec 和 autospec 都有一個更激進的版本,它可以阻止你設定不存在的屬性。如果你想確保你的程式碼也只設定有效的屬性,這很有用,但顯然它會阻止這種特定的情況。
>>> 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
作為 spec 將毫無用處,因為它不允許你訪問它的任何屬性或方法。由於 None
作為 spec 永遠不會有用,並且可能表示通常為其他型別的成員,因此 autospec 不會對設定為 None
的成員使用 spec。這些將只是普通的 mock(嗯 - MagicMocks)。
>>> class Something:
... member = None
...
>>> mock = create_autospec(Something)
>>> mock.member.foo.bar.baz()
<MagicMock name='mock.member.foo.bar.baz()' id='...'>
如果修改你的生產類以新增預設值不合你意,那麼還有更多選擇。其中一種方法是簡單地使用一個例項作為 spec,而不是類。另一種方法是建立生產類的子類,並將預設值新增到子類中,而不影響生產類。這兩種方法都需要你使用一個替代物件作為 spec。幸運的是,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 的類來建立一個 mock 例項並不會建立真正的例項。只有屬性查詢 - 以及對 dir()
的呼叫 - 才會執行。
密封 mock¶
- unittest.mock.seal(mock)¶
Seal 將停用在訪問被密封的 mock 的屬性或其已遞迴的任何屬性時自動建立 mock。
如果一個帶有名稱或 spec 的 mock 例項被分配給一個屬性,它將不會被視為密封鏈的一部分。這允許你阻止 seal 固定 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_value
和 wraps 的優先順序順序¶
它們的優先順序順序是
wraps
如果全部三個都被設定,mock 將從 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'
由於 None
是 side_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
優先順序順序將忽略此值,並且將移動到最後一個後繼者,即被包裝的物件。
由於實際呼叫正在被傳遞到被包裝的物件,建立此 mock 的例項將返回該類的真實例項。必須傳遞被包裝的物件所需的任何位置引數(如果有)。
>>> 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
即使你在初始化 mock 時一次設定所有三個,優先順序順序也保持不變。
>>> 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