unittest.mock — mock 物件函式庫

在 3.3 版被加入.

原始碼:Lib/unittest/mock.py


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

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

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

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

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

快速導引

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

>>>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 的行為。

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

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_setspec 的一個更嚴格的變體。如果使用spec_set,在 mock 上嘗試set 或取得不在傳遞給spec_set 的物件上的屬性將會引發AttributeError

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

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

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

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

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

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

    在 3.5 版被加入.

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

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

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

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

assert_called()

確認 mock 至少被呼叫一次。

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

在 3.6 版被加入.

assert_called_once()

確認 mock 只被呼叫一次。

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

在 3.6 版被加入.

assert_called_with(*args,**kwargs)

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

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

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

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

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

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

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

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

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

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

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

斷言 mock 從未被呼叫。

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

在 3.5 版被加入.

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

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

>>>mock=Mock(return_value=None)>>>mock('hello')>>>mock.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 toTrue 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 toTrue 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 thatreset_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_callsmock_calls 屬性中。

configure_mock(**kwargs)

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

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

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

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

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

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

__dir__()

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

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

_get_child_mock(**kw)

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

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

called

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

>>>mock=Mock(return_value=None)>>>mock.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_listmethod_callsmock_calls 的成員都是call 物件。這些都是元組,因此可以解包以取得各個引數並進行更複雜的斷言。參見calls as tuples

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

call_args_list

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

>>>mock=Mock(return_value=None)>>>mock()>>>mock(3,4)>>>mock(key='fish',next='w00t!')>>>mock.call_args_list[call(), call(3, 4), call(key='fish', next='w00t!')]>>>expected=[(),((3,4),),({'key':'fish','next':'w00t!'},)]>>>mock.call_args_list==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_valueside_effect 在不可呼叫的 mock 上無意義。

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

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

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

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

>>>m=MagicMock(attribute=3,other='fish')>>>m.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_effectreturn_value 的結果:

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

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

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

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

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

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

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

>>>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 ofMagicMock 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 thetimeout argument, or if unset by theThreadingMock.DEFAULT_TIMEOUT attribute, which defaults to blocking (None).

You can configure the global default timeout by settingThreadingMock.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 anAssertionError 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 anAssertionError 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 ofThreadingMock.

在 3.13 版被加入.

呼叫

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

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

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

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

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

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

>>>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_callsmock_calls 屬性中。這對於配置子代並將它們附加到上代,或將 mock 附加到記錄所有對子代的呼叫的上代並允許你對 mock 間的呼叫順序進行斷言非常有用:

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

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

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

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

>>>thing1=object()>>>thing2=object()>>>parent=MagicMock()>>>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')]
[1]

唯一的例外是魔術方法和屬性(具有前後雙底線)。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)。

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

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

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

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

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

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

備註

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

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

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

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

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

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

>>>@patch('__main__.SomeClass')...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'...

如果你使用specspec_setpatch() 正在取代一個類別,那麼被建立的 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_valueside_effect。它們在語法上不能直接作為關鍵字引數傳入,但是以它們作為鍵的字典仍然可以使用** 擴充為一個patch() 呼叫:

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

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

>>>@patch('sys.non_existing_attribute',42)...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()

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

patch.object

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

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

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

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

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

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

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

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

patch.dict

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

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

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

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

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

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

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

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

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

>>>foo={}>>>@patch.dict(foo,{'newkey':'newvalue'})...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() 可以做為裝飾器、類別裝飾器或情境管理器使用。引數specspec_setcreateautospecnew_callable 與在patch() 中的引數具有相同的意義。這些引數將應用於由patch.multiple() 完成的所有 patch。

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

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

>>>thing=object()>>>other=object()>>>@patch.multiple('__main__',thing=DEFAULT,other=DEFAULT)...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

一個典型的用法是在一個TestCasesetUp 方法中執行多個 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)

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

MagicMock 以及魔術方法支援

Mock 魔術方法

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

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

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

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

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

>>>mock=Mock()>>>mock.__enter__=Mock(return_value='foo')>>>mock.__exit__=Mock(return_value=False)>>>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 有兩個變體:MagicMockNonCallableMagicMock

classunittest.mock.MagicMock(*args,**kw)

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

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

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

classunittest.mock.NonCallableMagicMock(*args,**kw)

MagicMock 的不可呼叫版本。

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

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

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

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

方法及其預設值:

  • __lt__NotImplemented

  • __gt__NotImplemented

  • __le__NotImplemented

  • __ge__NotImplemented

  • __int__1

  • __contains__False

  • __len__0

  • __iter__iter([])

  • __exit__False

  • __aexit__False

  • __complex__1j

  • __float__1.0

  • __bool__True

  • __index__1

  • __hash__:mock 的預設雜湊

  • __str__:mock 的預設字串

  • __sizeof__:mock 的預設 sizeof

舉例來說:

>>>mock=MagicMock()>>>int(mock)1>>>len(mock)0>>>list(mock)[]>>>object()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__

[2]

魔術方法應該在類別而不是實例上被查找。不同版本的 Python 對於這條規則的適用並不一致。支援的協定方法應適用於所有支援的 Python 版本。

[3]

該函式基本上與類別掛鉤,但每個Mock 實例都與其他實例保持隔離。

輔助函式

sentinel(哨兵)

unittest.mock.sentinel

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

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

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

在測試時,有時你需要測試特定物件是否作為引數被傳遞給另一個方法或回傳。建立命名的哨兵物件來測試這一點是常見的。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_argscall_args_listmock_callsmethod_calls 相比,call() 是一個用於進行更簡單的斷言的輔助物件。call() 也可以與assert_has_calls() 一起使用。

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

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

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

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

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

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

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

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

>>>m=MagicMock(return_value=None)>>>m(1,2,3,arg='one',arg2='two')>>>kall=m.call_args>>>kall.args(1, 2, 3)>>>kall.kwargs{'arg': 'one', 'arg2': 'two'}>>>kall.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_setTrue,則嘗試設定規格物件上不存在的屬性將引發AttributeError

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

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

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

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

ANY

unittest.mock.ANY

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

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

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

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

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

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

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

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

>>>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='...'>
[4]

這只適用於類別或已經實例化的物件。呼叫一個被 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_effectreturn_valuewraps 的優先順序

它們的優先順序是:

  1. side_effect

  2. return_value

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

由於Noneside_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