unittest.mock
— mock 物件函式庫¶
在 3.3 版被加入.
unittest.mock
在 Python 中是一個用於進行測試的函式庫。 它允許你用 mock 物件在測試中替換部分系統,並判定它們是如何被使用的。
unittest.mock
提供了一個以Mock
為核心的類別,去除在測試中建立大量 stubs 的需求。 在執行動作之後,你可以判定哪些 method (方法)/屬性被使用,以及有哪些引數被呼叫。 你還可以用常規的方式指定回傳值與設定所需的屬性。
此外,mock 還提供了一個patch()
裝飾器,用於 patching 測試範圍內對 module(模組)以及 class(類別)級別的屬性,以及用於建立唯一物件的sentinel
。有關如何使用Mock
、MagicMock
和patch()
的一些範例,請參閱快速導引。
Mock 被設計用於與unittest
一起使用,並且基於 「action(操作) -> assertion(判定)」 模式,而不是許多 mocking 框架使用的 「record(記錄) -> replay(重播)」 模式。
對於早期版本的 Python,有一個 backport(向後移植的)unittest.mock
可以使用,可從 PyPI 下載mock。
快速導引¶
Mock
和MagicMock
物件在你存取它們時建立所有屬性和 method(方法),並儲存它們如何被使用的詳細訊息。你可以配置它們,以指定回傳值或限制可用的屬性,然後對它們的使用方式做出判定:
>>>fromunittest.mockimportMagicMock>>>thing=ProductionClass()>>>thing.method=MagicMock(return_value=3)>>>thing.method(3,4,5,key='value')3>>>thing.method.assert_called_with(3,4,5,key='value')
side_effect
允許你執行 side effects,包含在 mock 被呼叫時引發例外:
>>>fromunittest.mockimportMock>>>mock=Mock(side_effect=KeyError('foo'))>>>mock()Traceback (most recent call last):...KeyError:'foo'
>>>values={'a':1,'b':2,'c':3}>>>defside_effect(arg):...returnvalues[arg]...>>>mock.side_effect=side_effect>>>mock('a'),mock('b'),mock('c')(1, 2, 3)>>>mock.side_effect=[5,4,3,2,1]>>>mock(),mock(),mock()(5, 4, 3)
Mock 有許多其他方法可以讓你配置與控制它的行為。例如,spec 引數可以配置 mock ,讓其從另一個物件取得規格。嘗試讀取 mock 中不存在於規格中的屬性或方法將會失敗,並出現AttributeError
。
patch()
裝飾器/情境管理器可以在測試中簡單的 mock 模組中的類別或物件。被指定的物件在測試期間會被替換為 mock(或其他物件),並在測試結束時恢復:
>>>fromunittest.mockimportpatch>>>@patch('module.ClassName2')...@patch('module.ClassName1')...deftest(MockClass1,MockClass2):...module.ClassName1()...module.ClassName2()...assertMockClass1ismodule.ClassName1...assertMockClass2ismodule.ClassName2...assertMockClass1.called...assertMockClass2.called...>>>test()
備註
當你巢狀使用 patch 裝飾器時,mock 傳遞到被裝飾函式的順序會跟其被應用的順序相同(一般Python 應用裝飾器的順序)。這意味著由下而上,因此在上面的範例中,module.ClassName1
的 mock 會先被傳入。
使用patch()
時,需注意的是你得在被查找物件的命名空間中(in the namespace where they are looked up)patch 物件。這通常很直接,但若需要快速導引,請參閱該 patch 何處。
裝飾器patch()
也可以在 with 陳述式中被用來作為情境管理器:
>>>withpatch.object(ProductionClass,'method',return_value=None)asmock_method:...thing=ProductionClass()...thing.method(1,2,3)...>>>mock_method.assert_called_once_with(1,2,3)
也有patch.dict()
,用於在測試範圍中設定 dictionary(字典)內的值,並在測試結束時將其恢復為原始狀態:
>>>foo={'key':'value'}>>>original=foo.copy()>>>withpatch.dict(foo,{'newkey':'newvalue'},clear=True):...assertfoo=={'newkey':'newvalue'}...>>>assertfoo==original
Mock 支援對 Python 的魔術方法的 mocking。最簡單使用魔術方法的方式是使用MagicMock
類別。它允許你執行以下操作:
>>>mock=MagicMock()>>>mock.__str__.return_value='foobarbaz'>>>str(mock)'foobarbaz'>>>mock.__str__.assert_called_with()
Mock 允許你將函式(或其他 Mock 實例)分配給魔術方法,並且它們將被適當地呼叫。MagicMock
類別是一個 Mock 的變體,它為你預先建好了所有魔術方法(好吧,所有有用的方法)。
以下是在一般 Mock 類別中使用魔術方法的範例:
>>>mock=Mock()>>>mock.__str__=Mock(return_value='wheeeeee')>>>str(mock)'wheeeeee'
為了確保測試中的 mock 物件與它們要替換的物件具有相同的 api,你可以使用自動規格。自動規格(auto-speccing)可以通過 patch 的autospec 引數或create_autospec()
函式來完成。自動規格建立的 mock 物件與它們要替換的物件具有相同的屬性和方法,並且任何函式和方法(包括建構函式)都具有與真實物件相同的呼叫簽名(call signature)。
這可以確保如果使用方法錯誤,你的 mock 會跟實際程式碼以相同的方式失敗:
>>>fromunittest.mockimportcreate_autospec>>>deffunction(a,b,c):...pass...>>>mock_function=create_autospec(function,return_value='fishy')>>>mock_function(1,2,3)'fishy'>>>mock_function.assert_called_once_with(1,2,3)>>>mock_function('wrong arguments')Traceback (most recent call last):...TypeError:missing a required argument: 'b'
create_autospec()
也可以用在類別上,它複製了__init__
方法的簽名,它也可以用在可呼叫物件上,其複製了__call__
方法的簽名。
Mock 類別¶
Mock
是一個彈性的的 mock 物件,旨在代替程式碼中 stubs 和 test doubles (測試替身)的使用。Mock 是可呼叫的,並在你存取它們時將屬性建立為新的 mock[1]。存取相同的屬性將永遠回傳相同的 mock。Mock 記錄了你如何使用它們,允許你判定你的程式碼對 mock 的行為。
MagicMock
是Mock
的子類別,其中所有魔術方法均已預先建立並可供使用。也有不可呼叫的變體,在你 mock 無法呼叫的物件時很有用:NonCallableMock
和NonCallableMagicMock
patch()
裝飾器可以輕鬆地用Mock
物件臨時替換特定模組中的類別。預設情況下,patch()
會為你建立一個MagicMock
。你可以使用patch()
的new_callable 引數指定Mock
的替代類別。
- classunittest.mock.Mock(spec=None,side_effect=None,return_value=DEFAULT,wraps=None,name=None,spec_set=None,unsafe=False,**kwargs)¶
建立一個新的
Mock
物件。Mock
接受數個可選的引數來指定 Mock 物件的行為:spec:這可以是字串的 list(串列),也可以是充當 mock 物件規格的現有物件(類別或實例)。如果傳入一個物件,則通過對該物件呼叫 dir 來形成字串的串列(不包括不支援的魔術屬性和方法)。存取不在此串列中的任何屬性都會引發
AttributeError
。如果spec 是一個物件(而不是一個字串的串列),那麼
__class__
會回傳 spec 物件的類別。這允許 mocks 通過isinstance()
測試。spec_set:spec 的一個更嚴格的變體。如果使用spec_set,在 mock 上嘗試set 或取得不在傳遞給spec_set 的物件上的屬性將會引發
AttributeError
。side_effect:每當呼叫 Mock 時要呼叫的函式,參見
side_effect
屬性,用於引發例外或動態變更回傳值。該函式使用與 mock 相同的引數呼叫,且除非它回傳DEFAULT
,否則此函式的回傳值將用作回傳值。side_effect 也可以是一個例外的類別或實例。在這種情況下,當呼叫 mock 時,該例外將被引發。
如果side_effect 是一個可疊代物件,那麼對 mock 的每次呼叫將回傳可疊代物件中的下一個值。
side_effect 可以通過將其設置為
None
來清除。return_value:當呼叫 mock 時回傳的值。預設情況下,這是一個新的 Mock(在首次存取時建立)。參見
return_value
屬性。unsafe:預設情況下,存取任何以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。這對於除錯很有用。此名稱將被傳播到子 mocks。
Mocks 還可以使用任意的關鍵字引數進行呼叫。這些關鍵字引數將在建立 mock 之後用於設定 mock 的屬性。欲知更多,請參見
configure_mock()
方法。- assert_called()¶
確認 mock 至少被呼叫一次。
>>>mock=Mock()>>>mock.method()<Mock name='mock.method()' id='...'>>>>mock.method.assert_called()
在 3.6 版被加入.
- assert_called_once()¶
確認 mock 只被呼叫一次。
>>>mock=Mock()>>>mock.method()<Mock name='mock.method()' id='...'>>>>mock.method.assert_called_once()>>>mock.method()<Mock name='mock.method()' id='...'>>>>mock.method.assert_called_once()Traceback (most recent call last):...AssertionError:Expected 'method' to have been called once. Called 2 times.Calls: [call(), call()].
在 3.6 版被加入.
- assert_called_with(*args,**kwargs)¶
這個方法是一個便利的方式,用來斷言最後一次呼叫是以特定方式進行的:
>>>mock=Mock()>>>mock.method(1,2,3,test='wow')<Mock name='mock.method()' id='...'>>>>mock.method.assert_called_with(1,2,3,test='wow')
- assert_called_once_with(*args,**kwargs)¶
確認 mock 只被呼叫一次,且該次呼叫使用了指定的引數。
>>>mock=Mock(return_value=None)>>>mock('foo',bar='baz')>>>mock.assert_called_once_with('foo',bar='baz')>>>mock('other',bar='values')>>>mock.assert_called_once_with('other',bar='values')Traceback (most recent call last):...AssertionError:Expected 'mock' to be called once. Called 2 times.Calls: [call('foo', bar='baz'), call('other', bar='values')].
- assert_any_call(*args,**kwargs)¶
斷言 mock 已經被使用指定的引數呼叫。
這個斷言在 mock 曾經被呼叫過時通過,不同於
assert_called_with()
和assert_called_once_with()
,他們針對的是最近的一次的呼叫,而且對於assert_called_once_with()
,最近一次的呼叫還必須也是唯一一次的呼叫。>>>mock=Mock(return_value=None)>>>mock(1,2,arg='thing')>>>mock('some','thing','else')>>>mock.assert_any_call(1,2,arg='thing')
- assert_has_calls(calls,any_order=False)¶
斷言 mock 已經使用指定的呼叫方式來呼叫。此斷言會檢查
mock_calls
串列中的呼叫。如果any_order 為 false,那麼這些呼叫必須按照順序。在指定的呼叫之前或之後可以有額外的呼叫。
如果any_order 為 true,那麼這些呼叫可以以任何順序出現,但它們必須全部出現在
mock_calls
中。>>>mock=Mock(return_value=None)>>>mock(1)>>>mock(2)>>>mock(3)>>>mock(4)>>>calls=[call(2),call(3)]>>>mock.assert_has_calls(calls)>>>calls=[call(4),call(2),call(3)]>>>mock.assert_has_calls(calls,any_order=True)
- assert_not_called()¶
斷言 mock 從未被呼叫。
>>>m=Mock()>>>m.hello.assert_not_called()>>>obj=m.hello()>>>m.hello.assert_not_called()Traceback (most recent call last):...AssertionError:Expected 'hello' to not have been called. Called 1 times.Calls: [call()].
在 3.5 版被加入.
- reset_mock(*,return_value=False,side_effect=False)¶
reset_mock 方法重置 mock 物件上的所有呼叫屬性:
>>>mock=Mock(return_value=None)>>>mock('hello')>>>mock.calledTrue>>>mock.reset_mock()>>>mock.calledFalse
This can be useful where you want to make a series of assertions thatreuse the same object.
return_value parameter when set to
True
resetsreturn_value
:>>>mock=Mock(return_value=5)>>>mock('hello')5>>>mock.reset_mock(return_value=True)>>>mock('hello')<Mock name='mock()' id='...'>
side_effect parameter when set to
True
resetsside_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='...'>
Note that
reset_mock()
doesn't clear thereturn_value
,side_effect
or any child attributes you haveset using normal assignment by default.Child mocks are reset as well.
在 3.6 版的變更:reset_mock 函式新增了兩個僅限關鍵字引數 (keyword-only arguments)。
- mock_add_spec(spec,spec_set=False)¶
向 mock 增加一個規格 (spec)。spec 可以是一個物件或一個字串串列 (list of strings)。只有在spec 上的屬性才能作為 mock 的屬性被取得。
如果spec_set 為 true,那麼只能設定在規格中的屬性。
- attach_mock(mock,attribute)¶
將一個 mock 作為這個 Mock 的屬性附加,取代它的名稱和上代 (parent)。對附加的 mock 的呼叫將被記錄在這個 Mock 的
method_calls
和mock_calls
屬性中。
- configure_mock(**kwargs)¶
透過關鍵字引數在 mock 上設定屬性。
可以在使用 method(方法)呼叫時,使用標準點記法 (dot notation) 並將字典拆開,為 child mock 設定屬性、回傳值和 side effects:
>>>mock=Mock()>>>attrs={'method.return_value':3,'other.side_effect':KeyError}>>>mock.configure_mock(**attrs)>>>mock.method()3>>>mock.other()Traceback (most recent call last):...KeyError
同樣的事情可以在 mock 的建構函式呼叫中實現:
>>>attrs={'method.return_value':3,'other.side_effect':KeyError}>>>mock=Mock(some_attribute='eggs',**attrs)>>>mock.some_attribute'eggs'>>>mock.method()3>>>mock.other()Traceback (most recent call last):...KeyError
configure_mock()
的存在是為了在 mock 被建立後更容易進行組態設定。
- __dir__()¶
Mock
物件限制了dir(some_mock)
僅顯示有用的結果。對於具有spec 的 mock,這包含所有被允許的 mock 屬性。請參閱
FILTER_DIR
以了解這種過濾行為的作用,以及如何關閉它。
- _get_child_mock(**kw)¶
建立為了得到屬性和回傳值的 child mock。預設情況下,child mock 將與其上代是相同的型別。Mock 的子類別可能會想要置換此行為,以自定義 child mock 的建立方式。
對於不可呼叫的 mock,將使用可呼叫的變體,而不是任何的自定義子類別。
- called¶
一個 boolean(布林),表述 mock 物件是否已經被呼叫:
>>>mock=Mock(return_value=None)>>>mock.calledFalse>>>mock()>>>mock.calledTrue
- call_count¶
一個整數,告訴你 mock 物件被呼叫的次數:
>>>mock=Mock(return_value=None)>>>mock.call_count0>>>mock()>>>mock()>>>mock.call_count2
- 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_value3>>>mock()3
- side_effect¶
這可以是一個在呼叫 mock 時要呼叫的函式、一個可疊代物件,或者要引發的例外(類別或實例)。
如果你傳遞一個函式,它將被呼叫,其引數與 mock 相同,且除非該函式回傳
DEFAULT
單例 (singleton),否則對 mock 的呼叫將回傳函式回傳的任何值。如果函式回傳DEFAULT
,那麼 mock 將回傳其正常的回傳值(從return_value
得到)。如果你傳遞一個可疊代物件,它將被用於檢索一個疊代器,該疊代器必須在每次呼叫時產出 (yield) 一個值。這個值可以是要引發的例外實例,或者是對 mock 呼叫時要回傳的值(處理
DEFAULT
的方式與函式的狀況相同)。以下是一個引發例外的 mock 的範例(用於測試 API 的例外處理):
>>>mock=Mock()>>>mock.side_effect=Exception('Boom!')>>>mock()Traceback (most recent call last):...Exception:Boom!
使用
side_effect
回傳一連串值的範例:>>>mock=Mock()>>>mock.side_effect=[3,2,1]>>>mock(),mock(),mock()(3, 2, 1)
使用可被呼叫物件的範例:
>>>mock=Mock(return_value=3)>>>defside_effect(*args,**kwargs):...returnDEFAULT...>>>mock.side_effect=side_effect>>>mock()3
side_effect
可以在建構函式中設定。以下是一個範例,它將 mock 被呼叫時給的值加一並回傳:>>>side_effect=lambdavalue:value+1>>>mock=Mock(side_effect=side_effect)>>>mock(3)4>>>mock(-8)-7
將
side_effect
設定為None
可以清除它:>>>m=Mock(side_effect=KeyError,return_value=3)>>>m()Traceback (most recent call last):...KeyError>>>m.side_effect=None>>>m()3
- call_args¶
這會是
None
(如果 mock 尚未被呼叫),或是 mock 上次被呼叫時使用的引數。這將以元組的形式呈現:第一個成員 (member),其可以通過args
屬性存取,是 mock 被呼叫時傳遞的所有有序引數(或一個空元組)。第二個成員,其可以通過kwargs
屬性存取,是所有關鍵字引數(或一個空字典)。>>>mock=Mock(return_value=None)>>>print(mock.call_args)None>>>mock()>>>mock.call_argscall()>>>mock.call_args==()True>>>mock(3,4)>>>mock.call_argscall(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_argscall(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
物件。這些都是元組,因此可以解包以取得各個引數並進行更複雜的斷言。參見calls as tuples。在 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==expectedTrue
call_args_list
的成員都是call
物件。這些物件可以被拆包為元組,以取得各個引數。參見calls as tuples。
- method_calls¶
除了追蹤對自身的呼叫之外,mock 還會追蹤對方法和屬性的呼叫,以及它們(這些方法和屬性)的方法和屬性:
>>>mock=Mock()>>>mock.method()<Mock name='mock.method()' id='...'>>>>mock.property.method.attribute()<Mock name='mock.property.method.attribute()' id='...'>>>>mock.method_calls[call.method(), call.property.method.attribute()]
method_calls
的成員都是call
物件。這些物件可以拆包為元組,以取得各個引數。參見calls as tuples。
- mock_calls¶
mock_calls
記錄了所有 對 mock 物件的呼叫,包含其方法、魔術方法以及回傳值 mock。>>>mock=MagicMock()>>>result=mock(1,2,3)>>>mock.first(a=3)<MagicMock name='mock.first()' id='...'>>>>mock.second()<MagicMock name='mock.second()' id='...'>>>>int(mock)1>>>result(1)<MagicMock name='mock()()' id='...'>>>>expected=[call(1,2,3),call.first(a=3),call.second(),...call.__int__(),call()(1)]>>>mock.mock_calls==expectedTrue
method_calls
的成員都是call
物件。這些物件可以拆包為元組,以取得各個引數。參見calls as tuples。備註
mock_calls
記錄的方式意味著在進行巢狀呼叫時,上代 (ancestor) 呼叫的參數不會被記錄,因此在比較時它們將始終相等:>>>mock=MagicMock()>>>mock.top(a=3).bottom()<MagicMock name='mock.top().bottom()' id='...'>>>>mock.mock_calls[call.top(a=3), call.top().bottom()]>>>mock.mock_calls[-1]==call.top(a=-1).bottom()True
- __class__¶
通常,物件的
__class__
屬性會回傳它的型別。但對於擁有spec
的 mock 物件,__class__
會回傳 spec 的類別。這允許 mock 物件通過對它們所替代或偽裝的物件進行的isinstance()
測試:>>>mock=Mock(spec=3)>>>isinstance(mock,int)True
__class__
可以被指定,這允許 mock 通過isinstance()
檢查,而不需要強制使用 spec:>>>mock=Mock()>>>mock.__class__=dict>>>isinstance(mock,dict)True
- classunittest.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 魔術方法。細節請參考魔術方法。
Mock類別和patch()
裝飾器於組態時接受任意的關鍵字引數。對於patch()
裝飾器,這些關鍵字會傳遞給正在建立 mock 的建構函式。這些關鍵字引數用於配置 mock 的屬性:
>>>m=MagicMock(attribute=3,other='fish')>>>m.attribute3>>>m.other'fish'
Child mock 的回傳值和 side effect 可以使用使用點記法進行設置。由於你無法直接在呼叫中使用帶有點 (.) 的名稱,因此你必須建立一個字典並使用**
解包:
>>>attrs={'method.return_value':3,'other.side_effect':KeyError}>>>mock=Mock(some_attribute='eggs',**attrs)>>>mock.some_attribute'eggs'>>>mock.method()3>>>mock.other()Traceback (most recent call last):...KeyError
在匹配對 mock 的呼叫時,使用spec(或spec_set)建立的可呼叫 mock 將會內省 (introspect) 規格物件的簽名 (signature)。因此,它可以匹配實際呼叫的引數,無論它們是按位置傳遞還是按名稱傳遞:
>>>deff(a,b,c):pass...>>>mock=Mock(spec=f)>>>mock(1,2,c=3)<Mock name='mock()' id='140161580456576'>>>>mock.assert_called_with(1,2,3)>>>mock.assert_called_with(a=1,b=2,c=3)
這適用於assert_called_with()
、assert_called_once_with()
、assert_has_calls()
和assert_any_call()
。在使用Autospeccing(自動規格) 時,它還適用於 mock 物件的方法呼叫。
在 3.4 版的變更:對於已經設置了規格(spec)和自動規格(autospec)的 mock 物件,新增簽名內省功能。
- classunittest.mock.PropertyMock(*args,**kwargs)¶
一個理應在類別上當成
property
或其他descriptor 的 mock。PropertyMock
提供了__get__()
和__set__()
方法,因此你可以在它被提取時指定回傳值。從物件中提取
PropertyMock
實例會不帶任何引數呼叫 mock。設定它則會用設定的值來呼叫 mock:>>>classFoo:...@property...deffoo(self):...return'something'...@foo.setter...deffoo(self,value):...pass...>>>withpatch('__main__.Foo.foo',new_callable=PropertyMock)asmock_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.foo3>>>p.assert_called_once_with()
警示
If anAttributeError
is raised byPropertyMock
,it will be interpreted as a missing descriptor and__getattr__()
will be called on the parent mock:
>>>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__()
。
- classunittest.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 設定為非同步函式將導致在呼叫後回傳一個協程物件。>>>asyncdefasync_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
。>>>classExampleClass:...defsync_foo():...pass...asyncdefasync_foo():...pass...>>>a_mock=AsyncMock(ExampleClass)>>>a_mock.sync_foo<MagicMock name='mock.sync_foo' id='...'>>>>a_mock.async_foo<AsyncMock name='mock.async_foo' id='...'>>>>mock=Mock(ExampleClass)>>>mock.sync_foo<Mock name='mock.sync_foo' id='...'>>>>mock.async_foo<AsyncMock name='mock.async_foo' id='...'>
在 3.8 版被加入.
- assert_awaited()¶
斷言 mock 至少被等待過一次。請注意這與物件是否被呼叫是分開的,
await
關鍵字必須被使用:>>>mock=AsyncMock()>>>asyncdefmain(coroutine_mock):...awaitcoroutine_mock...>>>coroutine_mock=mock()>>>mock.calledTrue>>>mock.assert_awaited()Traceback (most recent call last):...AssertionError:Expected mock to have been awaited.>>>asyncio.run(main(coroutine_mock))>>>mock.assert_awaited()
- assert_awaited_once()¶
斷言 mock 正好被等待了一次。
>>>mock=AsyncMock()>>>asyncdefmain():...awaitmock()...>>>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)¶
斷言最後一次等待使用了指定的引數。
>>>mock=AsyncMock()>>>asyncdefmain(*args,**kwargs):...awaitmock(*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 只被等待了一次並使用了指定的引數。
>>>mock=AsyncMock()>>>asyncdefmain(*args,**kwargs):...awaitmock(*args,**kwargs)...>>>asyncio.run(main('foo',bar='bar'))>>>mock.assert_awaited_once_with('foo',bar='bar')>>>asyncio.run(main('foo',bar='bar'))>>>mock.assert_awaited_once_with('foo',bar='bar')Traceback (most recent call last):...AssertionError:Expected mock to have been awaited once. Awaited 2 times.
- assert_any_await(*args,**kwargs)¶
斷言 mock 曾經被使用指定的引數等待過。
>>>mock=AsyncMock()>>>asyncdefmain(*args,**kwargs):...awaitmock(*args,**kwargs)...>>>asyncio.run(main('foo',bar='bar'))>>>asyncio.run(main('hello'))>>>mock.assert_any_await('foo',bar='bar')>>>mock.assert_any_await('other')Traceback (most recent call last):...AssertionError:mock('other') await not found
- assert_has_awaits(calls,any_order=False)¶
斷言 mock 已被使用指定的呼叫進行等待。
await_args_list
串列將被檢查以確認等待的內容。如果any_order 為 false,則等待必須按照順序。指定的等待之前或之後可以有額外的呼叫。
如果any_order 為 true,則等待可以以任何順序出現,但它們必須全部出現在
await_args_list
中。>>>mock=AsyncMock()>>>asyncdefmain(*args,**kwargs):...awaitmock(*args,**kwargs)...>>>calls=[call("foo"),call("bar")]>>>mock.assert_has_awaits(calls)Traceback (most recent call last):...AssertionError:Awaits not found.Expected: [call('foo'), call('bar')]Actual: []>>>asyncio.run(main('foo'))>>>asyncio.run(main('bar'))>>>mock.assert_has_awaits(calls)
- assert_not_awaited()¶
斷言 mock 從未被等待。
>>>mock=AsyncMock()>>>mock.assert_not_awaited()
- reset_mock(*args,**kwargs)¶
參見
Mock.reset_mock()
。同時將await_count
設定為 0,await_args
設定為 None,並清除await_args_list
。
- await_count¶
一個整數,用來記錄 mock 物件已被等待的次數。
>>>mock=AsyncMock()>>>asyncdefmain():...awaitmock()...>>>asyncio.run(main())>>>mock.await_count1>>>asyncio.run(main())>>>mock.await_count2
- await_args¶
這可能是
None
(如果 mock 尚未被等待),或者是上次等待 mock 時使用的引數。與Mock.call_args
的功能相同。>>>mock=AsyncMock()>>>asyncdefmain(*args):...awaitmock(*args)...>>>mock.await_args>>>asyncio.run(main('foo'))>>>mock.await_argscall('foo')>>>asyncio.run(main('bar'))>>>mock.await_argscall('bar')
- await_args_list¶
這是一個按照順序記錄 mock 物件所有等待的串列(因此串列的長度表示該物件已被等待的次數)。在進行任何等待之前,此串列為空。
>>>mock=AsyncMock()>>>asyncdefmain(*args):...awaitmock(*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')]
- classunittest.mock.ThreadingMock(spec=None,side_effect=None,return_value=DEFAULT,wraps=None,name=None,spec_set=None,unsafe=False,*,timeout=UNSET,**kwargs)¶
A version of
MagicMock
for multithreading tests. TheThreadingMock
object provides extra methods to wait for a call tobe invoked, rather than assert on it immediately.The default timeout is specified by the
timeout
argument, or if unset by theThreadingMock.DEFAULT_TIMEOUT
attribute, which defaults to blocking (None
).You can configure the global default timeout by setting
ThreadingMock.DEFAULT_TIMEOUT
.- wait_until_called(*,timeout=UNSET)¶
等待直到 mock 被呼叫。
If a timeout was passed at the creation of the mock or if a timeoutargument is passed to this function, the function raises an
AssertionError
if the call is not performed in time.>>>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 被以特定引數呼叫。
If a timeout was passed at the creation of the mockthe function raises an
AssertionError
if the call is not performed in time.>>>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¶
Global default timeout in seconds to create instances of
ThreadingMock
.
在 3.13 版被加入.
呼叫¶
Mock 物件可被呼叫。呼叫將回傳設定為return_value
屬性的值。預設的回傳值是一個新的 Mock 物件;它會在第一次存取回傳值時(無論是顯式存取還是透過呼叫 Mock)被建立,但是這個回傳值會被儲存,之後每次都回傳同一個值。
對物件的呼叫會被記錄在如call_args
和call_args_list
等屬性中。
如果side_effect
被設定,那麼在呼叫被記錄後它才會被呼叫,所以如果side_effect
引發例外,呼叫仍然會被記錄。
呼叫 mock 時引發例外的最簡單方式是將side_effect
設定為例外類別或實例:
>>>m=MagicMock(side_effect=IndexError)>>>m(1,2,3)Traceback (most recent call last):...IndexError>>>m.mock_calls[call(1, 2, 3)]>>>m.side_effect=KeyError('Bang!')>>>m('two','three','four')Traceback (most recent call last):...KeyError:'Bang!'>>>m.mock_calls[call(1, 2, 3), call('two', 'three', 'four')]
如果side_effect
是一個函式,則該函式回傳的東西就是對 mock 的呼叫所回傳的值。side_effect
函式會使用與 mock 相同的引數被呼叫。這讓你可以根據輸入動態地變更呼叫的回傳值:
>>>defside_effect(value):...returnvalue+1...>>>m=MagicMock(side_effect=side_effect)>>>m(1)2>>>m(2)3>>>m.mock_calls[call(1), call(2)]
如果你希望 mock 仍然回傳預設的回傳值(一個新的 mock),或者是任何已設定的回傳值,有兩種方法可以實現。從side_effect
內部回傳return_value
,或回傳DEFAULT
:
>>>m=MagicMock()>>>defside_effect(*args,**kwargs):...returnm.return_value...>>>m.side_effect=side_effect>>>m.return_value=3>>>m()3>>>defside_effect(*args,**kwargs):...returnDEFAULT...>>>m.side_effect=side_effect>>>m()3
要刪除side_effect
,並恢復預設行為,將side_effect
設為None
:
>>>m=MagicMock(return_value=6)>>>defside_effect(*args,**kwargs):...return3...>>>m.side_effect=side_effect>>>m()3>>>m.side_effect=None>>>m()6
side_effect
也可以是任何可疊代的物件。對 mock 的重複呼叫將從可疊代物件中回傳值(直到疊代物件耗盡並引發StopIteration
為止):
>>>m=MagicMock(side_effect=[1,2,3])>>>m()1>>>m()2>>>m()3>>>m()Traceback (most recent call last):...StopIteration
如果可疊代物件中的任何成員是例外,則它們將被引發而不是被回傳:
>>>iterable=(33,ValueError,66)>>>m=MagicMock(side_effect=iterable)>>>m()33>>>m()Traceback (most recent call last):...ValueError>>>m()66
刪除屬性¶
Mock 物件會在需要時建立屬性。這使得它們可以假裝成任何種類的物件。
你可能希望一個 mock 物件在hasattr()
呼叫時回傳False
,或者在屬性被提取時引發AttributeError
。你可以通過將物件提供為 mock 的spec
來實現這一點,但這並不總是那麼好用。
你可以通過刪除屬性來「阻擋」它們。一旦刪除,再次存取該屬性將會引發AttributeError
。
>>>mock=MagicMock()>>>hasattr(mock,'m')True>>>delmock.m>>>hasattr(mock,'m')False>>>delmock.f>>>mock.fTraceback (most recent call last):...AttributeError:f
Mock 名稱與名稱屬性¶
由於 "name" 是傳遞給Mock
建構函式的引數,如果你想讓你的 mock 物件擁有 "name" 屬性,你不能在建立時直接傳遞它。有兩種替代方法。其中一個選擇是使用configure_mock()
:
>>>mock=MagicMock()>>>mock.configure_mock(name='my_name')>>>mock.name'my_name'
更簡單的方法是在 mock 建立後直接設定 "name" 屬性:
>>>mock=MagicMock()>>>mock.name="foo"
如同屬性一般附加 mock¶
當你將一個 mock 附加為另一個 mock 的屬性(或作為回傳值),它將成為該 mock 的「子代 (child)」。對子代的呼叫將被記錄在上代的method_calls
和mock_calls
屬性中。這對於配置子代並將它們附加到上代,或將 mock 附加到記錄所有對子代的呼叫的上代並允許你對 mock 間的呼叫順序進行斷言非常有用:
>>>parent=MagicMock()>>>child1=MagicMock(return_value=None)>>>child2=MagicMock(return_value=None)>>>parent.child1=child1>>>parent.child2=child2>>>child1(1)>>>child2(2)>>>parent.mock_calls[call.child1(1), call.child2(2)]
如果 mock 有 name 引數,則上述規則會有例外。這使你可以防止「親屬關係 (parenting)」的建立,假設因為某些原因你不希望這種狀況發生。
>>>mock=MagicMock()>>>not_a_child=MagicMock(name='not-a-child')>>>mock.attribute=not_a_child>>>mock.attribute()<MagicMock name='not-a-child()' id='...'>>>>mock.mock_calls[]
由patch()
為你建立的 mock 會自動被賦予名稱。若要將具有名稱的 mock 附加到上代,你可以使用attach_mock()
方法:
>>>thing1=object()>>>thing2=object()>>>parent=MagicMock()>>>withpatch('__main__.thing1',return_value=None)aschild1:...withpatch('__main__.thing2',return_value=None)aschild2:...parent.attach_mock(child1,'child1')...parent.attach_mock(child2,'child2')...child1('one')...child2('two')...>>>parent.mock_calls[call.child1('one'), call.child2('two')]
唯一的例外是魔術方法和屬性(具有前後雙底線)。Mock 不會建立這些,而是會引發AttributeError
。這是因為直譯器通常會隱式地要求這些方法,在期望得到一個魔術方法卻獲得一個新的 Mock 物件時,會讓直譯器非常困惑。如果你需要魔術方法的支援,請參閱魔術方法。
Patchers¶
patch 裝飾器僅用於在裝飾的函式範圍內對物件進行 patch。它們會自動為你處理 patch 的中止,即使有異常被引發也是如此。所有這些函式也可以在 with 陳述式中使用,或者作為類別裝飾器使用。
patch¶
備註
關鍵是要在正確的命名空間進行 patch。請參閱where to patch 一節。
- unittest.mock.patch(target,new=DEFAULT,spec=None,create=False,spec_set=None,autospec=None,new_callable=None,**kwargs)¶
patch()
充當函式裝飾器、類別裝飾器或情境管理器。在函式或 with 陳述式的內部,目標會被 patch 成一個新的物件。當函式或 with 陳述式結束時,patch 就會被解除。如果new 被省略,則如果被 patch 的物件是非同步函式,目標會被替換為
AsyncMock
,反之則替換為MagicMock
。如果patch()
做為裝飾器使用且省略了new,則所建立的 mock 會作為額外的引數傳遞給被裝飾的函式。如果patch()
作為情境管理器使用,則所建立的 mock 將由情境管理器回傳。target 應該是以
'package.module.ClassName'
形式出現的字串。target 會被引入並用new 物件替換指定的物件,因此target 必須可從你呼叫patch()
的環境中引入。target 在執行被裝飾的函式時被引入,而不是在裝飾器作用時 (decoration time)。spec 和spec_set 關鍵字引數會傳遞給
MagicMock
,如果 patch 正在為你建立一個。此外,你還可以傳遞
spec=True
或spec_set=True
,這將導致 patch 將被 mock 的物件作為 spec/spec_set 物件傳遞。new_callable 允許你指定一個不同的類別或可呼叫的物件,用於被呼叫並建立new 物件。預設情況下,對於非同步函式使用
AsyncMock
,而對於其他情況則使用MagicMock
。spec 的一種更強大的形式是autospec。如果你設定
autospec=True
,則該 mock 將使用被替換物件的規格來建立。該 mock 的所有屬性也將具有被替換物件的對應屬性的規格。被 mock 的方法和函式將檢查其引數,如果呼叫時引數與規格不符(被使用錯誤的簽名 (signature) 呼叫),將引發TypeError
。對於替換類別的 mock,它們的回傳值(即 'instance')將具有與類別相同的規格。請參閱create_autospec()
函式和Autospeccing(自動規格)。你可以用
autospec=some_object
替代autospec=True
,以使用任意物件作為規格,而不是被替換的物件。預設情況下,
patch()
將無法取代不存在的屬性。如果你傳入create=True
,且屬性不存在,則當被 patch 的函式被呼叫時,patch 將為你建立該屬性,並在被 patch 的函式結束後再次刪除它。這對於撰寫針對你的生產程式碼在執行環境建立的屬性的測試時非常有用。此功能預設為關閉,因為這可能會相當危險。開啟這個功能後,你可以對於實際上不存在的 API 撰寫會通過的測試!備註
在 3.5 版的變更:如果你正在 patch 模組中的內建函式,那麼你不需要傳遞
create=True
,它預設會被加入。patch 可以做為
TestCase
類別的裝飾器使用。它透過裝飾類別中的每個測試方法來運作。當你的測試方法共享一組常見的 patch 時,這會減少繁冗的代碼。patch()
通過搜尋以patch.TEST_PREFIX
開頭的方法名來尋找測試。預設情況下會是'test'
,這與unittest
尋找測試的方式相匹配。你可以通過設定patch.TEST_PREFIX
來指定別的前綴。透過 with 陳述式,Patch 可以做為情境管理器使用。patch 適用於 with 陳述式之後的縮排區塊。如果你使用 "as",則被 patch 的物件將被綁定到 "as" 後面的名稱;如果
patch()
正在為你建立一個 mock 物件,這會非常有用。patch()
接受任意的關鍵字引數。如果被 patch 的物件是非同步的,這些將會被傳遞給AsyncMock
,如果是同步的則會傳遞給MagicMock
,或如果指定了new_callable,則傳遞給它。patch.dict(...)
、patch.multiple(...)
和patch.object(...)
可用於其餘的使用情境。
patch()
作為函式裝飾器,為你建立 mock 並將其傳遞給被裝飾的函式:
>>>@patch('__main__.SomeClass')...deffunction(normal_argument,mock_class):...print(mock_classisSomeClass)...>>>function(None)True
Patch 一個類別會以MagicMock
實例取代該類別。如果該類別在被測試的程式碼中實例化,那麼它將是會被使用的 mock 的return_value
。
如果該類別被實例化多次,你可以使用side_effect
來每次回傳一個新的 mock。 或者你可以將return_value 設定成你想要的任何值。
若要配置被 patch 的類別的實例方法的回傳值,你必須在return_value
上進行配置。例如:
>>>classClass:...defmethod(self):...pass...>>>withpatch('__main__.Class')asMockClass:...instance=MockClass.return_value...instance.method.return_value='foo'...assertClass()isinstance...assertClass().method()=='foo'...
如果你使用spec 或spec_set 且patch()
正在取代一個類別,那麼被建立的 mock 的回傳值將具有相同的規格。:
>>>Original=Class>>>patcher=patch('__main__.Class',spec=True)>>>MockClass=patcher.start()>>>instance=MockClass()>>>assertisinstance(instance,Original)>>>patcher.stop()
當你想要為被建立的 mock 使用一個替代的類別取代預設的MagicMock
時,new_callable 引數非常有用。例如,如果你想要一個NonCallableMock
被使用:
>>>thing=object()>>>withpatch('__main__.thing',new_callable=NonCallableMock)asmock_thing:...assertthingismock_thing...thing()...Traceback (most recent call last):...TypeError:'NonCallableMock' object is not callable
另一個用法是用一個io.StringIO
實例替換一個物件:
>>>fromioimportStringIO>>>deffoo():...print('Something')...>>>@patch('sys.stdout',new_callable=StringIO)...deftest(mock_stdout):...foo()...assertmock_stdout.getvalue()=='Something\n'...>>>test()
當patch()
為你建立 mock 時,通常你需要做的第一件事就是配置 mock。其中一些配置可以在對 patch 的呼叫中完成。你傳遞到呼叫中的任何關鍵字都將用於在被建立的 mock 上設定屬性:
>>>patcher=patch('__main__.thing',first='one',second='two')>>>mock_thing=patcher.start()>>>mock_thing.first'one'>>>mock_thing.second'two'
除了被建立的 mock 上的屬性外,還可以配置 child mock 的return_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)...deftest():...assertsys.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)...deftest(mock_stdout):...assertsys.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 一個物件(目標)上的命名成員(屬性)。
patch.object()
可以做為裝飾器、類別裝飾器或情境管理器使用。引數new、spec、create、spec_set、autospec 和new_callable 與在patch()
中的引數具有相同的意義。與patch()
一樣,patch.object()
接受任意關鍵字引數來配置它所建立的 mock 物件。當作為類別裝飾器使用時,
patch.object()
會遵循patch.TEST_PREFIX
來選擇要包裝的方法。
你可以使用三個引數或兩個引數來呼叫patch.object()
。三個引數的形式接受要被 patch 的物件、屬性名稱和要替換掉屬性的物件。
當使用兩個引數的形式呼叫時,你會省略要替換的物件,一個 mock 會為你建立並將其作為額外的引數傳遞給被裝飾的函式:
>>>@patch.object(SomeClass,'class_method')...deftest(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)¶
Patch 字典或類字典的物件,並在測試後將字典回復到其原本的狀態。
in_dict 可以是一個字典或一個類對映的容器。如果它是一個對映,那麼它至少必須支援取得、設定、刪除項目以及對鍵的疊代。
in_dict 也可以是指定字典名稱的字串,然後透過 import 來取得該字典。
values 可以是要設定的值的字典。values 也可以是
(key,value)
對 (pairs) 的可疊代物件。如果clear 為 true,則在設定新值之前字典將被清除。
也可以使用任意關鍵字引數呼叫
patch.dict()
以在字典中設定值。在 3.8 版的變更:
patch.dict()
現在在做為情境管理器使用時回傳被 patch 的字典。
patch.dict()
可以做為情境管理器、裝飾器或類別裝飾器使用:
>>>foo={}>>>@patch.dict(foo,{'newkey':'newvalue'})...deftest():...assertfoo=={'newkey':'newvalue'}...>>>test()>>>assertfoo=={}
當作為類別裝飾器使用時,patch.dict()
會遵循patch.TEST_PREFIX
(預設為'test'
)來選擇要包裝的方法:
>>>importos>>>importunittest>>>fromunittest.mockimportpatch>>>@patch.dict('os.environ',{'newkey':'newvalue'})...classTestSample(unittest.TestCase):...deftest_sample(self):...self.assertEqual(os.environ['newkey'],'newvalue')
如果你想在測試中使用不同的前綴,你可以透過設定patch.TEST_PREFIX
來告知 patcher 使用不同的前綴。請參閱TEST_PREFIX 以得知如何修改前綴的更多內容。
patch.dict()
可用於在字典中新增成員,或單純地讓測試更改字典,並確保在測試結束時將字典回復原狀。
>>>foo={}>>>withpatch.dict(foo,{'newkey':'newvalue'})aspatched_foo:...assertfoo=={'newkey':'newvalue'}...assertpatched_foo=={'newkey':'newvalue'}...# You can add, update or delete keys of foo (or patched_foo, it's the same dict)...patched_foo['spam']='eggs'...>>>assertfoo=={}>>>assertpatched_foo=={}
>>>importos>>>withpatch.dict('os.environ',{'newkey':'newvalue'}):...print(os.environ['newkey'])...newvalue>>>assert'newkey'notinos.environ
可以在patch.dict()
呼叫中使用關鍵字來設定字典中的值:
>>>mymodule=MagicMock()>>>mymodule.function.return_value='fish'>>>withpatch.dict('sys.modules',mymodule=mymodule):...importmymodule...mymodule.function('some','args')...'fish'
patch.dict()
可以與實際上不是字典的類字典物件一起使用。最低限度它們必須支援項目的取得、設定、刪除以及疊代或隸屬資格檢測。這對應到魔術方法中的__getitem__()
、__setitem__()
、__delitem__()
以及__iter__()
或__contains__()
。
>>>classContainer:...def__init__(self):...self.values={}...def__getitem__(self,name):...returnself.values[name]...def__setitem__(self,name,value):...self.values[name]=value...def__delitem__(self,name):...delself.values[name]...def__iter__(self):...returniter(self.values)...>>>thing=Container()>>>thing['one']=1>>>withpatch.dict(thing,one=2,two=3):...assertthing['one']==2...assertthing['two']==3...>>>assertthing['one']==1>>>assertlist(thing)==['one']
patch.multiple¶
- patch.multiple(target,spec=None,create=False,spec_set=None,autospec=None,new_callable=None,**kwargs)¶
在一次呼叫中執行多個 patch。它接受被 patch 的物件(作為物件或透過 import 取得物件的字串)和 patch 的關鍵字引數:
withpatch.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。當作為類別裝飾器使用時,
patch.multiple()
遵循patch.TEST_PREFIX
來選擇要包裝的方法。
如果你想要patch.multiple()
為你建立 mock,那麼你可以使用DEFAULT
作為值。如果你使用patch.multiple()
作為裝飾器,那麼被建立的 mock 將透過關鍵字傳遞到被裝飾的函式中。:
>>>thing=object()>>>other=object()>>>@patch.multiple('__main__',thing=DEFAULT,other=DEFAULT)...deftest_function(thing,other):...assertisinstance(thing,MagicMock)...assertisinstance(other,MagicMock)...>>>test_function()
patch.multiple()
可以與其他patch
裝飾器巢狀使用,但需要將透過關鍵字傳遞的引數放在patch()
建立的任何標準引數之後:
>>>@patch('sys.exit')...@patch.multiple('__main__',thing=DEFAULT,other=DEFAULT)...deftest_function(mock_exit,other,thing):...assert'other'inrepr(other)...assert'thing'inrepr(thing)...assert'exit'inrepr(mock_exit)...>>>test_function()
如果patch.multiple()
作為情境管理器使用,則情境管理器回傳的值是一個字典,其中被建立的 mock 會按名稱作為其鍵值:
>>>withpatch.multiple('__main__',thing=DEFAULT,other=DEFAULT)asvalues:...assert'other'inrepr(values['other'])...assert'thing'inrepr(values['thing'])...assertvalues['thing']isthing...assertvalues['other']isother...
patch 方法:啟動與停止¶
所有的 patcher 都有start()
與stop()
方法。這使得在setUp
方法中進行 patch 或在你想要在沒有巢狀使用裝飾器或 with 陳述式的情況下進行多個 patch 時變得更簡單。
要使用它們,請像平常一樣呼叫patch()
、patch.object()
或patch.dict()
,並保留對回傳的patcher
物件的參照。之後你就可以呼叫start()
將 patch 準備就緒,並呼叫stop()
來取消 patch。
如果你使用patch()
為你建立 mock,那麼它將透過呼叫patcher.start
回傳。:
>>>patcher=patch('package.module.ClassName')>>>frompackageimportmodule>>>original=module.ClassName>>>new_mock=patcher.start()>>>assertmodule.ClassNameisnotoriginal>>>assertmodule.ClassNameisnew_mock>>>patcher.stop()>>>assertmodule.ClassNameisoriginal>>>assertmodule.ClassNameisnotnew_mock
一個典型的用法是在一個TestCase
的setUp
方法中執行多個 patch:
>>>classMyTest(unittest.TestCase):...defsetUp(self):...self.patcher1=patch('package.module.Class1')...self.patcher2=patch('package.module.Class2')...self.MockClass1=self.patcher1.start()...self.MockClass2=self.patcher2.start()......deftearDown(self):...self.patcher1.stop()...self.patcher2.stop()......deftest_something(self):...assertpackage.module.Class1isself.MockClass1...assertpackage.module.Class2isself.MockClass2...>>>MyTest('test_something').run()
警示
如果你使用這個技巧,你必須確保透過呼叫stop
來 "取消" patch。這可能會比你想像的還要複雜一點,因為如果有例外在setUp
中被引發,則tearDown
就不會被呼叫。unittest.TestCase.addCleanup()
會讓這稍微簡單一點:
>>>classMyTest(unittest.TestCase):...defsetUp(self):...patcher=patch('package.module.Class')...self.MockClass=patcher.start()...self.addCleanup(patcher.stop)......deftest_something(self):...assertpackage.module.Classisself.MockClass...
作為額外的好處,你不再需要保留對patcher
物件的參照。
也可以使用patch.stopall()
來停止所有已啟動的 patch。
- patch.stopall()¶
停止所有運作的 patch。只停止以
start
啟動的 patch。
patch 內建函式¶
你可以 patch 模組內的任何內建函式。以下範例 patch 內建函式ord()
:
>>>@patch('__main__.ord')...deftest(mock_ord):...mock_ord.return_value=101...print(ord('c'))...>>>test()101
TEST_PREFIX¶
所有 patcher 都可以作為類別裝飾器使用。以這種方式使用時,它們包裝了類別上的每個測試方法。Patcher 將'test'
開頭的方法認定為測試方法。這與unittest.TestLoader
預設尋找測試方法的方式相同。
你可能會想為你的測試使用不同的前綴。你可以透過設定patch.TEST_PREFIX
來告知 patcher 使用不同的前綴:
>>>patch.TEST_PREFIX='foo'>>>value=3>>>>>>@patch('__main__.value','not three')...classThing:...deffoo_one(self):...print(value)...deffoo_two(self):...print(value)...>>>>>>Thing().foo_one()not three>>>Thing().foo_two()not three>>>value3
巢狀使用 Patch 裝飾器¶
如果你想執行多個 patch,那麼你可以簡單地堆疊裝飾器。
你可以使用這個模式來堆疊多個 patch 裝飾器:
>>>@patch.object(SomeClass,'class_method')...@patch.object(SomeClass,'static_method')...deftest(mock1,mock2):...assertSomeClass.static_methodismock1...assertSomeClass.class_methodismock2...SomeClass.static_method('foo')...SomeClass.class_method('bar')...returnmock1,mock2...>>>mock1,mock2=test()>>>mock1.assert_called_once_with('foo')>>>mock2.assert_called_once_with('bar')
請注意,裝飾器是從底部向上應用的。這是 Python 應用裝飾器的標準方式。被建立的 mock 傳遞到測試函式中的順序與此順序相同。
該 patch 何處¶
patch()
的工作原理是(暫時)將name 指向的物件變更為另一個物件。可以有許多 name 指向任何單一物件,因此為了使 patch 起作用,你必須確保你 patch 了被測試系統使用的 name。
基本原則是在物件被查找的位置進行 patch,該位置不一定與其被定義的位置相同。幾個範例將有助於闡明這一點。
想像一下,我們想要測試一個專案,其結構如下:
a.py->DefinesSomeClassb.py->fromaimportSomeClass->some_functioninstantiatesSomeClass
現在我們想要測試some_function
,但我們想使用patch()
mockSomeClass
。問題是,當我們 import 模組 b 時(我們必須這樣做),它會從模組 a importSomeClass
。如果我們使用patch()
來 mocka.SomeClass
,那麼它對我們的測試就不會有任何影響;模組 b 已經有了一個真實的SomeClass
的參照 ,看起來我們的 patch 並沒有任何效果。
關鍵是在使用(或查找它)的地方 patchSomeClass
。在這個情況下,some_function
實際上會在我們 import 它的模組 b 中查找SomeClass
。這裡的 patch 應該長得像這樣:
@patch('b.SomeClass')
然而,考慮另一種情況,其中模組 b 並不是使用fromaimportSomeClass
,而是importa
,然後some_function
使用a.SomeClass
。這兩種 import 形式都很常見。在這種情況下,我們想要 patch 的類別正在其模組中被查找,因此我們必須 patcha.SomeClass
:
@patch('a.SomeClass')
Patch 描述器與代理物件 (Proxy Objects)¶
patch 和patch.object 都正確地 patch 和還原描述器:類別方法、靜態方法以及屬性。你應該在類別 而不是實例上 patch 它們。它們還可以使用代理屬性存取的一些物件,例如django 設定物件。
MagicMock 以及魔術方法支援¶
Mock 魔術方法¶
Mock
支援 mock Python 協定方法,其也被稱作"魔術方法"。這允許 mock 物件替換容器或實作 Python 協定的其他物件。
由於魔術方法被查找的方式與一般的方法[2] 不同,因此專門實作了此支援方式。這代表著僅有特定的魔術方法被此方式支援。現在已支援清單中已經幾乎包含了所有魔術方法。如果你需要 mock 任何魔術方法而其尚未被支援,請讓我們知道。
你可以透過將你感興趣的方法設定為函式或 mock 實例來 mock 魔術方法。如果你使用函式,那麼它必須將self
作為第一個引數[3]。
>>>def__str__(self):...return'fooble'...>>>mock=Mock()>>>mock.__str__=__str__>>>str(mock)'fooble'
>>>mock=Mock()>>>mock.__str__=Mock()>>>mock.__str__.return_value='fooble'>>>str(mock)'fooble'
>>>mock=Mock()>>>mock.__iter__=Mock(return_value=iter([]))>>>list(mock)[]
一個用法是在with
陳述式中 mock 作為情境管理器使用的物件:
>>>mock=Mock()>>>mock.__enter__=Mock(return_value='foo')>>>mock.__exit__=Mock(return_value=False)>>>withmockasm:...assertm=='foo'...>>>mock.__enter__.assert_called_with()>>>mock.__exit__.assert_called_with(None,None,None)
對魔術方法的呼叫並不會出現在method_calls
中,它們會被記錄在mock_calls
內。
備註
如果你使用spec關鍵字引數來建立一個 mock,則嘗試設定規格中未包含的魔術方法將引發一個AttributeError
。
已支援的魔術方法的完整列表是:
__hash__
、__sizeof__
、__repr__
和__str__
__dir__
、__format__
和__subclasses__
__round__
、__floor__
、__trunc__
和__ceil__
比較方法:
__lt__
、__gt__
、__le__
、__ge__
、__eq__
和__ne__
容器方法:
__getitem__
、__setitem__
、__delitem__
、__contains__
、__len__
、__iter__
、__reversed__
和__missing__
情境管理器:
__enter__
、__exit__
、__aenter__
和__aexit__
一元數值方法:
__neg__
、__pos__
和__invert__
數值方法(包括右側 (right hand) 和原地 (in-place) 變體):
__add__
、__sub__
、__mul__
、__matmul__
、__truediv__
、__floordiv__
、__mod__
、__divmod__
、__lshift__
、__rshift__
、__and__
、__xor__
、__or__
和__pow__
數值轉換方法:
__complex__
、__int__
、__float__
和__index__
描述器方法:
__get__
、__set__
和__delete__
Pickling:
__reduce__
、__reduce_ex__
、__getinitargs__
、__getnewargs__
、__getstate__
和__setstate__
檔案系統路徑表示法:
__fspath__
非同步疊代方法:
__aiter__
和__anext__
在 3.8 版的變更:新增對於os.PathLike.__fspath__()
的支援。
在 3.8 版的變更:新增對於__aenter__
、__aexit__
、__aiter__
和__anext__
的支援。
以下方法存在,但「不」被支援,因為它們在被 mock 使用時,會無法動態設定,或可能導致問題:
__getattr__
、__setattr__
、__init__
和__new__
__prepare__
、__instancecheck__
、__subclasscheck__
、__del__
Magic Mock¶
MagicMock
有兩個變體:MagicMock
和NonCallableMagicMock
。
- classunittest.mock.MagicMock(*args,**kw)¶
MagicMock
是Mock
的子類別,其預設具有大多數魔術方法的實作。你可以使用MagicMock
,而無需自行配置魔術方法。建構函式參數的意義與
Mock
中的參數相同。如果你使用spec 或spec_set 引數,那麼只有規格中存在的魔術方法會被建立。
- classunittest.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 的預設 sizeof
舉例來說:
>>>mock=MagicMock()>>>int(mock)1>>>len(mock)0>>>list(mock)[]>>>object()inmockFalse
__eq__()
和__ne__()
這兩個相等的方法是特別的。它們使用side_effect
屬性對識別性 (identity) 進行預設的相等比較,除非你變更它們的回傳值以回傳其他內容:
>>>MagicMock()==3False>>>MagicMock()!=3True>>>mock=MagicMock()>>>mock.__eq__.return_value=True>>>mock==3True
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 版本。
[3]該函式基本上與類別掛鉤,但每個Mock
實例都與其他實例保持隔離。
輔助函式¶
sentinel(哨兵)¶
- unittest.mock.sentinel¶
哨兵
物件提供了一種為你的測試提供獨特物件的便利方式。當你使用名稱存取屬性時,屬性會根據需要被建立。存取相同的屬性將始終回傳相同的物件。回傳的物件會具有合適的 repr,讓測試失敗的訊息是可閱讀的。
在測試時,有時你需要測試特定物件是否作為引數被傳遞給另一個方法或回傳。建立命名的哨兵物件來測試這一點是常見的。sentinel
提供了一種此類建立和測試物件識別性的便利方式。
在這個例子中,我們 monkey patchmethod
以回傳sentinel.some_object
:
>>>real=ProductionClass()>>>real.method=Mock(name="method")>>>real.method.return_value=sentinel.some_object>>>result=real.method()>>>assertresultissentinel.some_object>>>resultsentinel.some_object
DEFAULT¶
- unittest.mock.DEFAULT¶
DEFAULT
物件是一個預先建立的哨兵(實際上是sentinel.DEFAULT
)。它可以被side_effect
函式使用來表示正常的回傳值應該被使用。
call¶
- unittest.mock.call(*args,**kwargs)¶
與
call_args
、call_args_list
、mock_calls
和method_calls
相比,call()
是一個用於進行更簡單的斷言的輔助物件。call()
也可以與assert_has_calls()
一起使用。>>>m=MagicMock(return_value=None)>>>m(1,2,a='foo',b='bar')>>>m()>>>m.call_args_list==[call(1,2,a='foo',b='bar'),call()]True
- call.call_list()¶
對於表示多個呼叫的 call 物件,
call_list()
回傳所有中間呼叫以及最終呼叫的串列。
call_list
在對「鍊接呼叫 (chained calls)」進行斷言時特別有用。鍊接呼叫是在單行程式碼進行的多次呼叫。這會導致 mock 上的mock_calls
中出現多個項目。手動建構呼叫序列會相當單調乏味。
call_list()
可以從同一個鍊接呼叫建構呼叫序列:
>>>m=MagicMock()>>>m(1).method(arg='foo').other('bar')(2.0)<MagicMock name='mock().method().other()()' id='...'>>>>kall=call(1).method(arg='foo').other('bar')(2.0)>>>kall.call_list()[call(1), call().method(arg='foo'), call().method().other('bar'), call().method().other()(2.0)]>>>m.mock_calls==kall.call_list()True
取決於它的建構方式,一個call
物件會是(位置引數, 關鍵字引數)的元組,或是 (名稱, 位置引數, 關鍵字引數) 的元組。當你自己建構它們時,這並不是那麼有趣,但是Mock.call_args
、Mock.call_args_list
和Mock.mock_calls
屬性中的call
物件可以被內省以取得它們包含的各個引數。
Mock.call_args
和Mock.call_args_list
中的call
物件是(位置引數, 關鍵字引數)的二元組,而Mock.mock_calls
中的call
物件以及你自己建立的call
物件是(名稱, 位置引數, 關鍵字引數)的三元組。
你可以利用它們作為元組的特性來提取單個引數,以進行更複雜的內省和斷言。位置引數是一個元組(如果沒有位置引數則為空元組),關鍵字引數是一個字典:
>>>m=MagicMock(return_value=None)>>>m(1,2,3,arg='one',arg2='two')>>>kall=m.call_args>>>kall.args(1, 2, 3)>>>kall.kwargs{'arg': 'one', 'arg2': 'two'}>>>kall.argsiskall[0]True>>>kall.kwargsiskall[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'}>>>nameism.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 的建構函式。
請參閱Autospeccing(自動規格) 以得知如何以create_autospec()
使用自動規格以及如何在patch()
中使用autospec 引數的範例。
在 3.8 版的變更:如果目標是一個非同步函式,create_autospec()
現在會回傳一個AsyncMock
。
ANY¶
- unittest.mock.ANY¶
有時你可能需要對 mock 的呼叫中的某些引數進行斷言,但你不在意其他的某些引數,或想將它們單獨從call_args
中取出並進行更加複雜的斷言。
要忽略某些引數,你可以傳入對所有物件來說都相等的物件。那麼無論傳入什麼內容,對assert_used_with()
和assert_used_once_with()
的呼叫都會成功。
>>>mock=Mock(return_value=None)>>>mock('foo',bar=object())>>>mock.assert_called_once_with('foo',bar=ANY)
ANY
也可以用來與呼叫串列進行比較,例如mock_calls
:
>>>m=MagicMock(return_value=None)>>>m(1)>>>m(1,2)>>>m(object())>>>m.mock_calls==[call(1),call(1,2),ANY]True
ANY
不只能與呼叫物件比較,其也可以在測試斷言中使用:
classTestStringMethods(unittest.TestCase):deftest_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', ...>>>fromurllibimportrequest>>>dir(Mock(spec=request))['AbstractBasicAuthHandler', 'AbstractDigestAuthHandler', 'AbstractHTTPHandler', 'BaseHandler', ...
許多不是很有用的(對Mock
來說是私有的,而不是被 mock 的東西)底線和雙底線前綴屬性已從在Mock
上呼叫dir()
的結果中濾除。如果你不喜歡這種特性,可以透過設定模組級別開關FILTER_DIR
來將其關閉:
>>>fromunittestimportmock>>>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()
用途的 mock 的輔助函式。它適用於直接呼叫或用作情境管理器的open()
。mock 引數是要配置的 mock 物件。如果其為
None
(預設值),那麼就會為你建立一個MagicMock
,其 API 限制在標準檔案處理上可用的方法或屬性。read_data 是檔案處理方法
read()
、readline()
和readlines()
的回傳字串。對這些方法的呼叫將從read_data 取得資料,直到資料耗盡。對這些方法的 mock 非常單純:每次呼叫mock 時,read_data 都會倒回到起點。如果你需要對提供給測試程式碼的資料進行更多控制,你會需要自行客製化這個 mock。如果這樣還不夠,PyPI 上的其中一個記憶體內檔案系統 (in-memory filesystem) 套件可以提供用於測試的真實檔案系統。在 3.4 版的變更:新增對
readline()
和readlines()
的支援。read()
的 mock 更改為消耗read_data 而不是在每次呼叫時回傳它。在 3.5 版的變更:現在,每次呼叫mock 時都會重置read_data。
在 3.8 版的變更:新增
__iter__()
到實作中,以便使疊代(例如在 for 迴圈中)正確地消耗read_data。
使用open()
作為情境管理器是確保檔案處理正確關閉的好方式,且這種方式正在變得普遍:
withopen('/some/path','w')asf:f.write('something')
問題是,即使你 mock 了對open()
的呼叫,它也是作為情境管理器使用的回傳物件(且其__enter__()
和__exit__()
已被呼叫)。
使用MagicMock
mock 情境管理器相當常見並且精細,因此輔助函式就非常有用:
>>>m=mock_open()>>>withpatch('__main__.open',m):...withopen('foo','w')ash:...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')
以及讀取檔案:
>>>withpatch('__main__.open',mock_open(read_data='bibble'))asm:...withopen('foo')ash:...result=h.read()...>>>m.assert_called_once_with('foo')>>>assertresult=='bibble'
Autospeccing(自動規格)¶
自動規格以 mock 現有的spec
功能作為基礎。它將 mock 的 api 限制為原始物件(規格)的 api,但它是遞迴的(惰性 (lazily) 實現),因此 mock 的屬性僅具有與規格的屬性相同的 api。此外,被 mock 的函式/方法具有與原始的函式/方法相同的呼叫簽名,因此如果它們被不正確地呼叫,就會引發TypeError
。
在解釋自動規格如何運作之前,我們先解釋為什麼需要它。
Mock
是一個非常強大且靈活的物件,但它有一個常見的 mock 缺陷。如果你重構某些程式碼或重新命名成員等,則任何仍然使用舊 api 但使用 mock 而非真實物件的程式碼測試仍然會通過。這意味著即使你的程式碼壞了,但測試仍可以全部通過。
在 3.5 版的變更:在 3.5 之前,當測試應該引發錯誤時,斷言單字中存在拼字錯誤的測驗會默默地通過。你仍可以透過將unsafe=True
傳遞給 Mock 來實作此行為。
謹記這是你需要有整合測試和單元測試的另一個原因。單獨測試所有內容都很好,但如果你不測試你的單元是如何「連接在一起」的,那麼測試還是有機會發現很多錯誤。
unittest.mock
已經提供了一個功能來幫助解決這個問題,其稱為 speccing。如果你使用類別或實例作為 mock 的spec
,那麼你在 mock 上只能存取真實類別中存在的屬性:
>>>fromurllibimportrequest>>>mock=Mock(spec=request.Request)>>>mock.assret_called_with# Intentional typo!Traceback (most recent call last):...AttributeError:Mock object has no attribute 'assret_called_with'
該規格僅適用於 mock 本身,因此在 mock 上的任何方法仍然有相同的問題:
>>>mock.header_items()<mock.Mock object at 0x...>>>>mock.header_items.assret_called_with()# Intentional typo!
自動規格解決了這個問題。你可以將autospec=True
傳遞給patch()
/patch.object()
或使用create_autospec()
函式建立帶有規格的 mock。如果你對patch()
使用autospec=True
引數,則被取代的物件將作為規格物件使用。因為規格是「惰性」完成的(規格是在 mock 被存取時作為屬性被建立的),所以你可以將它與非常複雜或深度巢狀使用的物件(例如連續引用的模組)一起使用,而不會過於影響性能。
這是一個正在使用的例子:
>>>fromurllibimportrequest>>>patcher=patch('__main__.request',autospec=True)>>>mock_request=patcher.start()>>>requestismock_requestTrue>>>mock_request.Request<MagicMock name='request.Request' spec='Request' id='...'>
你可以看到request.Request
有一個規格。request.Request
在建構函式中接受兩個引數(其中之一是self)。如果我們錯誤地呼叫它,會發生以下情況:
>>>req=request.Request()Traceback (most recent call last):...TypeError:<lambda>() takes at least 2 arguments (1 given)
此規格也適用於實例化的類別(即有規格的 mock 的回傳值):
>>>req=request.Request('foo')>>>req<NonCallableMagicMock name='request.Request()' spec='Request' id='...'>
Request
物件不是可呼叫物件,因此實例化我們 mock out 的request.Request
的回傳值是不可呼叫的 mock。規格到位後,斷言中的任何拼字錯誤都會引發正確的錯誤:
>>>req.add_header('spam','eggs')<MagicMock name='request.Request().add_header()' id='...'>>>>req.add_header.assret_called_with# Intentional typo!Traceback (most recent call last):...AttributeError:Mock object has no attribute 'assret_called_with'>>>req.add_header.assert_called_with('spam','eggs')
在許多情況下,你只需要將autospec=True
新增至現有的patch()
呼叫中,然後就可以防止因拼字錯誤和 api 變更而導致的錯誤。
除了透過patch()
使用autospec 之外,還有一個create_autospec()
用於直接建立有自動規格的 mock:
>>>fromurllibimportrequest>>>mock_request=create_autospec(request)>>>mock_request.Request('foo','bar')<NonCallableMagicMock name='mock.Request()' spec='Request' id='...'>
然而,這並非完全沒有限制,這就是為什麼它不是預設的行為。為了理解規格物件上有哪些可用屬性,autospec 必須內省(存取屬性)規格。當你遍歷 mock 上的屬性時,原始物件的對應遍歷正在默默發生。如果你的規格物件具有可以觸發程式碼執行的屬性或描述器,那麼你可能無法使用 autospec。換句話說,設計你的物件讓內省是安全的[4] 會比較好。
一個更嚴重的問題是,在__init__()
方法中建立實例屬性是常見的,而其根本不存在於類別中。autospec 無法知道任何動態建立的屬性,並將 api 限制為可見的屬性。:
>>>classSomething:...def__init__(self):...self.a=33...>>>withpatch('__main__.Something',autospec=True):...thing=Something()...thing.a...Traceback (most recent call last):...AttributeError:Mock object has no attribute 'a'
有幾種不同的方法可以解決這個問題。最簡單但可能有點煩人的方法是在建立後簡單地在 mock 上設定所需的屬性。因為雖然autospec 不允許你取得規格中不存在的屬性,但是它不會阻止你設定它們:
>>>withpatch('__main__.Something',autospec=True):...thing=Something()...thing.a=33...
spec 和autospec 有一個更激進的版本,它會確實地阻止你設定不存在的屬性。如果你想確保你的程式碼僅能設定有效的屬性,那麼這會很有用,但顯然它也順便阻止了這個特殊情況:
>>>withpatch('__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__()
中設定預設屬性,那麼透過類別屬性(當然在實例之間共用)提供它們也會更快。例如:
classSomething:a=33
這就帶來了另一個問題。為稍後將成為不同型別的物件的成員提供預設值None
是相對常見的。None
作為規格是無用的,因為它不允許你存取其上的任何屬性或方法。由於None
作為規格永遠不會有用,並且可能表示通常屬於其他型別的成員,因此自動規格不會對設定為None
的成員使用規格。這些會只是普通的 mock(通常是 MagicMocks):
>>>classSomething:...member=None...>>>mock=create_autospec(Something)>>>mock.member.foo.bar.baz()<MagicMock name='mock.member.foo.bar.baz()' id='...'>
如果修改正式生產 (production) 類別以新增預設值不符合你的喜好,那麼還有更多選擇。其中之一就是簡單地使用實例作為規格而不是使用類別。另一種是建立一個正式生產類別的子類別,並將預設值新增至子類別中,而不影響正式生產類別。這兩個都需要你使用替代物件作為規格。值得慶幸的是patch()
支援這一點 - 你可以簡單地將替代物件作為autospec 引數傳遞:
>>>classSomething:...def__init__(self):...self.a=33...>>>classSomethingForTest(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)¶
當存取被密封的 mock 的屬性或其任何已經遞迴 mock 的屬性時,seal 將停用 mock 的自動建立。
如果將具有名稱或規格的 mock 實例指派給屬性,則不會出現在密封鏈中。這表示可藉由固定 mock 物件的一部分來防止密封。:
>>>mock=Mock()>>>mock.submock.attribute1=2>>>mock.not_submock=mock.Mock(name="sample_name")>>>seal(mock)>>>mock.new_attribute# This will raise AttributeError.>>>mock.submock.attribute2# This will raise AttributeError.>>>mock.not_submock.attribute2# This won't raise.
在 3.7 版被加入.
side_effect
、return_value
和wraps 的優先順序¶
它們的優先順序是:
如果這三個都有設定,mock 將會回傳來自side_effect
的值,並忽略return_value
和被包裝物件。如果設定了任兩項,則優先順序較高的一項將回傳該值。無論先設定哪個順序,優先順序都保持不變。
>>>fromunittest.mockimportMock>>>classOrder:...@staticmethod...defget_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
,它將被忽略,並且優先順序被移動到後面一個以獲得要回傳的值。
>>>fromunittest.mockimportDEFAULT>>>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_valuesentinel.DEFAULT>>>order_mock.get_value.return_valuesentinel.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()isNoneTrue
即使你在初始化 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