"unittest.mock" --- 入門指南
****************************

在 3.3 版被加入.


使用 Mock 的方式
================


使用 Mock 來 patching 方法
--------------------------

"Mock" 物件的常見用法包含：

* Patching 方法

* 記錄在物件上的方法呼叫

你可能會想要取代一個物件上的方法，以便檢查系統的另一部分是否使用正確的
引數呼叫它：

>>> real = SomeClass()
>>> real.method = MagicMock(name='method')
>>> real.method(3, 4, 5, key='value')
<MagicMock name='method()' id='...'>

一旦我們的 mock 已經被使用（例如在這個範例中的 "real.method"），它就有
了方法和屬性，允許你對其使用方式進行斷言 (assertions)。

備註:

  在大多數的範例中，"Mock" 和 "MagicMock" 類別是可以互換的。不過由於
  "MagicMock" 是功能更強大的類別，因此通常它是一個更好的選擇。

一旦 mock 被呼叫，它的 "called" 屬性將被設定為 "True"。更重要的是，我
們可以使用 "assert_called_with()" 或 "assert_called_once_with()" 方法
來檢查它是否被使用正確的引數來呼叫。

這個範例測試呼叫 "ProductionClass().method" 是否導致對 "something" 方
法的呼叫：

>>> class ProductionClass:
...     def method(self):
...         self.something(1, 2, 3)
...     def something(self, a, b, c):
...         pass
...
>>> real = ProductionClass()
>>> real.something = MagicMock()
>>> real.method()
>>> real.something.assert_called_once_with(1, 2, 3)


對物件的方法呼叫使用 mock
-------------------------

在上一個範例中，我們直接對物件上的方法進行 patch，以檢查它是否被正確呼
叫。另一個常見的用法是將一個物件傳遞給一個方法（或受測系統的某一部分）
，然後檢查它是否以正確的方式被使用。

下面是一個單純的 "ProductionClass"，含有一個 "closer" 方法。如果它被傳
入一個物件，它就會呼叫此物件中的 "close"。

>>> class ProductionClass:
...     def closer(self, something):
...         something.close()
...

因此，為了對此進行測試，我們需要傳遞一個具有 "close" 方法的物件，並檢
查它是否被正確的呼叫。

>>> real = ProductionClass()
>>> mock = Mock()
>>> real.closer(mock)
>>> mock.close.assert_called_with()

我們不必做任何額外的事情來為 mock 提供 'close' 方法，存取 close 會建立
它。因此，如果 'close' 並未被呼叫過，在測試中存取 'close' 就會建立它，
但 "assert_called_with()" 就會引發一個失敗的例外。


Mock 類別
---------

一個常見的使用案例是在測試的時候 mock 被程式碼實例化的類別。當你 patch
一個類別時，該類別就會被替換為 mock。實例是透過*呼叫類別*建立的。這代
表你可以透過查看被 mock 的類別的回傳值來存取「mock 實例」。

在下面的範例中，我們有一個函式 "some_function"，它實例化 "Foo" 並呼叫
它的方法。對 "patch()" 的呼叫將類別 "Foo" 替換為一個 mock。"Foo" 實例
是呼叫 mock 的結果，因此它是透過修改 mock "return_value" 來配置的。：

   >>> def some_function():
   ...     instance = module.Foo()
   ...     return instance.method()
   ...
   >>> with patch('module.Foo') as mock:
   ...     instance = mock.return_value
   ...     instance.method.return_value = 'the result'
   ...     result = some_function()
   ...     assert result == 'the result'


命名你的 mock
-------------

為你的 mock 命名可能會很有用。這個名稱會顯示在 mock 的 repr 中，且當
mock 出現在測試的失敗訊息中時，名稱會很有幫助。該名稱也會傳播到 mock
的屬性或方法：

>>> mock = MagicMock(name='foo')
>>> mock
<MagicMock name='foo' id='...'>
>>> mock.method
<MagicMock name='foo.method' id='...'>


追蹤所有呼叫
------------

通常你會想要追蹤對一個方法的多個呼叫。"mock_calls" 屬性記錄對 mock 的
子屬性以及其子屬性的所有呼叫。

>>> mock = MagicMock()
>>> mock.method()
<MagicMock name='mock.method()' id='...'>
>>> mock.attribute.method(10, x=53)
<MagicMock name='mock.attribute.method()' id='...'>
>>> mock.mock_calls
[call.method(), call.attribute.method(10, x=53)]

如果你對 "mock_calls" 做出斷言並且有任何不預期的方法被呼叫，則斷言將失
敗。這很有用，因為除了斷言你期望的呼叫已經進行之外，你還可以檢查它們是
否按正確的順序進行，並且沒有多餘的呼叫：

你可以使用 "call" 物件來建構串列以與 "mock_calls" 進行比較：

>>> expected = [call.method(), call.attribute.method(10, x=53)]
>>> mock.mock_calls == expected
True

然而，回傳 mock 的呼叫的參數不會被記錄，這代表在巢狀呼叫中，無法追蹤用
於建立上代的參數 important 的值：

>>> m = Mock()
>>> m.factory(important=True).deliver()
<Mock name='mock.factory().deliver()' id='...'>
>>> m.mock_calls[-1] == call.factory(important=False).deliver()
True


設定回傳值和屬性
----------------

在 mock 物件上設定回傳值非常簡單：

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

當然，你可以對 mock 上的方法執行相同的操作：

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

回傳值也可以在建構函式中設定：

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

如果你需要在 mock 上進行屬性設置，只需執行以下操作：

>>> mock = Mock()
>>> mock.x = 3
>>> mock.x
3

有時你想要 mock 更複雜的情況，例如
"mock.connection.cursor().execute("SELECT 1")"。如果我們希望此呼叫回傳
一個串列，那麼我們就必須配置巢狀呼叫的結果。

如下所示，我們可以使用 "call" 在「鍊接呼叫 (chained call)」中建構呼叫
的集合，以便在之後輕鬆的進行斷言：

>>> mock = Mock()
>>> cursor = mock.connection.cursor.return_value
>>> cursor.execute.return_value = ['foo']
>>> mock.connection.cursor().execute("SELECT 1")
['foo']
>>> expected = call.connection.cursor().execute("SELECT 1").call_list()
>>> mock.mock_calls
[call.connection.cursor(), call.connection.cursor().execute('SELECT 1')]
>>> mock.mock_calls == expected
True

正是對 ".call_list()" 的呼叫將我們的呼叫物件轉換為代表鍊接呼叫的呼叫串
列。


透過 mock 引發例外
------------------

一個有用的屬性是 "side_effect"。如果將其設定為例外類別或實例，則當
mock 被呼叫時將引發例外。

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


Side effect 函式以及可疊代物件
------------------------------

"side_effect" 也可以設定為函式或可疊代物件。"side_effect" 作為可疊代物
件的使用案例是：當你的 mock 將會被多次呼叫，且你希望每次呼叫回傳不同的
值。當你將 "side_effect" 設定為可疊代物件時，對 mock 的每次呼叫都會傳
回可疊代物件中的下一個值：

>>> mock = MagicMock(side_effect=[4, 5, 6])
>>> mock()
4
>>> mock()
5
>>> mock()
6

對於更進階的使用案例，例如根據 mock 被呼叫的內容動態變更回傳值，可以將
"side_effect" 設成一個函式。該函式會使用與 mock 相同的引數被呼叫。函式
回傳的內容就會是呼叫回傳的內容：

>>> vals = {(1, 2): 1, (2, 3): 2}
>>> def side_effect(*args):
...     return vals[args]
...
>>> mock = MagicMock(side_effect=side_effect)
>>> mock(1, 2)
1
>>> mock(2, 3)
2


Mock 非同步可疊代物件
---------------------

從 Python 3.8 開始，"AsyncMock" 和 "MagicMock" 支援透過 "__aiter__" 來
mock Asynchronous Iterators。"__aiter__" 的 "return_value" 屬性可用來
設定用於疊代的回傳值。

>>> mock = MagicMock()  # AsyncMock also works here
>>> mock.__aiter__.return_value = [1, 2, 3]
>>> async def main():
...     return [i async for i in mock]
...
>>> asyncio.run(main())
[1, 2, 3]


Mock 非同步情境管理器
---------------------

從 Python 3.8 開始，"AsyncMock" 和 "MagicMock" 支援透過 "__aenter__"
和 "__aexit__" 來 mock Asynchronous Context Managers。預設情況下，
"__aenter__" 和 "__aexit__" 是回傳非同步函式的 "AsyncMock" 實例。

>>> class AsyncContextManager:
...     async def __aenter__(self):
...         return self
...     async def __aexit__(self, exc_type, exc, tb):
...         pass
...
>>> mock_instance = MagicMock(AsyncContextManager())  # AsyncMock also works here
>>> async def main():
...     async with mock_instance as result:
...         pass
...
>>> asyncio.run(main())
>>> mock_instance.__aenter__.assert_awaited_once()
>>> mock_instance.__aexit__.assert_awaited_once()


從現有物件建立 mock
-------------------

過度使用 mock 的一個問題是，它將你的測試與 mock 的實作結合在一起，而不
是與真實的程式碼結合。假設你有一個實作 "some_method" 的類別，在另一個
類別的測試中，你提供了一個 mock 的物件，其*也*提供了 "some_method"。如
果之後你重構第一個類別，使其不再具有 "some_method" - 那麼即使你的程式
碼已經損壞，你的測試也將繼續通過！

"Mock" 允許你使用 *spec* 關鍵字引數提供一個物件作為 mock 的規格。對
mock 存取規格物件上不存在的方法或屬性將立即引發一個屬性錯誤。如果你更
改規格的實作，那麼使用該類別的測試將立即失敗，而無需在這些測試中實例化
該類別。

>>> mock = Mock(spec=SomeClass)
>>> mock.old_method()
Traceback (most recent call last):
   ...
AttributeError: object has no attribute 'old_method'

使用規格還可以更聰明地匹配對 mock 的呼叫，無論引數是作為位置引數還是命
名引數傳遞：

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

如果你希望這種更聰明的匹配也可以應用於 mock 上的方法呼叫，你可以使用自
動規格。

如果你想要一種更強大的規格形式來防止設定任意屬性以及取得它們，那麼你可
以使用 *spec_set* 而不是 *spec*。


使用 side_effect 回傳各別檔案內容
---------------------------------

"mock_open()" 是用於 patch "open()" 方法。"side_effect" 可以用來在每次
呼叫回傳一個新的 mock 物件。這可以用於回傳儲存在字典中的各別檔案的不同
內容：

   DEFAULT = "default"
   data_dict = {"file1": "data1",
                "file2": "data2"}

   def open_side_effect(name):
       return mock_open(read_data=data_dict.get(name, DEFAULT))()

   with patch("builtins.open", side_effect=open_side_effect):
       with open("file1") as file1:
           assert file1.read() == "data1"

       with open("file2") as file2:
           assert file2.read() == "data2"

       with open("file3") as file2:
           assert file2.read() == "default"


Patch 裝飾器
============

備註:

  使用 "patch()" 時，需注意的是你得在被查找物件的命名空間中（in the
  namespace where they are looked up）patch 物件。這通常很直接，但若需
  要快速導引，請參閱該 patch 何處。

測試中的常見需求是 patch 類別屬性或模組屬性，例如 patch 一個內建函式（
built-in）或 patch 模組中的類別以測試它是否已實例化。模組和類別實際上
是全域的，因此在測試後必須撤銷它們的 patch，否則 patch 將延續到其他測
試中並導致難以診斷問題。

mock 為此提供了三個方便的裝飾器："patch()"、"patch.object()" 和
"patch.dict()"。"patch" 接受單一字串，格式為
"package.module.Class.attribute"，用來指定要 patch 的屬性。同時它也可
以接受你想要替換的屬性（或類別或其他）的值。"patch.object" 接受一個物
件和你想要 patch 的屬性的名稱，同時也可以接受要 patch 的值。

"patch.object"：

   >>> original = SomeClass.attribute
   >>> @patch.object(SomeClass, 'attribute', sentinel.attribute)
   ... def test():
   ...     assert SomeClass.attribute == sentinel.attribute
   ...
   >>> test()
   >>> assert SomeClass.attribute == original

   >>> @patch('package.module.attribute', sentinel.attribute)
   ... def test():
   ...     from package.module import attribute
   ...     assert attribute is sentinel.attribute
   ...
   >>> test()

如果你要 patch 一個模組（包括 "builtins"），請使用 "patch()" 而非
"patch.object()"：

>>> mock = MagicMock(return_value=sentinel.file_handle)
>>> with patch('builtins.open', mock):
...     handle = open('filename', 'r')
...
>>> mock.assert_called_with('filename', 'r')
>>> assert handle == sentinel.file_handle, "incorrect file handle returned"

如有需要，模組名稱可以含有 "."，形式為 "package.module"：

   >>> @patch('package.module.ClassName.attribute', sentinel.attribute)
   ... def test():
   ...     from package.module import ClassName
   ...     assert ClassName.attribute == sentinel.attribute
   ...
   >>> test()

一個好的模式是實際裝飾測試方法本身：

>>> class MyTest(unittest.TestCase):
...     @patch.object(SomeClass, 'attribute', sentinel.attribute)
...     def test_something(self):
...         self.assertEqual(SomeClass.attribute, sentinel.attribute)
...
>>> original = SomeClass.attribute
>>> MyTest('test_something').test_something()
>>> assert SomeClass.attribute == original

如果你想使用一個 mock 進行 patch，你可以使用僅帶有一個引數的 "patch()"
（或帶有兩個引數的 "patch.object()"）。Mock 將被建立並被傳遞到測試函式
/ 方法中：

>>> class MyTest(unittest.TestCase):
...     @patch.object(SomeClass, 'static_method')
...     def test_something(self, mock_method):
...         SomeClass.static_method()
...         mock_method.assert_called_with()
...
>>> MyTest('test_something').test_something()

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

   >>> class MyTest(unittest.TestCase):
   ...     @patch('package.module.ClassName1')
   ...     @patch('package.module.ClassName2')
   ...     def test_something(self, MockClass2, MockClass1):
   ...         self.assertIs(package.module.ClassName1, MockClass1)
   ...         self.assertIs(package.module.ClassName2, MockClass2)
   ...
   >>> MyTest('test_something').test_something()

當你巢狀使用 patch 裝飾器時，mock 傳遞到被裝飾函式的順序會跟其被應用的
順序相同（一般 *Python* 應用裝飾器的順序）。這意味著由下而上，因此在上
面的範例中，"module.ClassName2" 的 mock 會先被傳入。

也有 "patch.dict()"，用於在測試範圍中設定字典內的值，並在測試結束時將
其恢復為原始狀態：

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

"patch"、"patch.object" 和 "patch.dict" 都可以用來作為情境管理器。

當你使用 "patch()" 為你建立一個 mock 時，你可以使用 with 陳述式的 "as"
形式來取得 mock 的參照：

>>> class ProductionClass:
...     def method(self):
...         pass
...
>>> with patch.object(ProductionClass, 'method') as mock_method:
...     mock_method.return_value = None
...     real = ProductionClass()
...     real.method(1, 2, 3)
...
>>> mock_method.assert_called_with(1, 2, 3)

另外，“patch”、“patch.object” 和 “patch.dict” 也可以用來作為類別裝飾器
。以這種方式使用時，與將裝飾器單獨應用於每個名稱以 “test” 開頭的方法相
同。


更多範例
========

以下是一些更進階一點的情境的範例。


Mock 鍊接呼叫
-------------

一旦你了解了 "return_value" 屬性，mock 鍊接呼叫其實就很簡單了。當一個
mock 第一次被呼叫，或者你在它被呼叫之前取得其 "return_value" 時，一個
新的 "Mock" 就會被建立。

這代表你可以透過查問 "return_value" mock 來了解一個對被 mock 的物件的
呼叫回傳的物件是如何被使用的：

>>> mock = Mock()
>>> mock().foo(a=2, b=3)
<Mock name='mock().foo()' id='...'>
>>> mock.return_value.foo.assert_called_with(a=2, b=3)

這裡只需一個簡單的步驟即可進行配置並對鍊接呼叫進行斷言。當然，另一種選
擇是先以更容易被測試的方式撰寫程式碼...

所以，假設我們有一些程式碼，看起來大概像這樣：

>>> class Something:
...     def __init__(self):
...         self.backend = BackendProvider()
...     def method(self):
...         response = self.backend.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
...         # more code

假設 "BackendProvider" 已經經過充分測試，那麼我們該如何測試 "method()"
？具體來說，我們要測試程式碼部分 "# more code" 是否以正確的方式使用
"response" 物件。

由於此呼叫鍊是從實例屬性進行的，因此我們可以在 "Something" 實例上
monkey patch "backend" 屬性。在這種特定的情況下，我們只對最終呼叫
"start_call" 的回傳值感興趣，因此我們不需要做太多配置。我們假設它傳回
的物件是類檔案物件 (file-like)，因此我們會確保我們的 response 物件使用
內建的 "open()" 作為其 "spec"。

為此，我們建立一個 mock 實例作為我們的 mock backend，並為其建立一個
mock response 物件。要將 response 設定為最後的 "start_call" 的回傳值，
我們可以這樣做：

   mock_backend.get_endpoint.return_value.create_call.return_value.start_call.return_value = mock_response

我們可以使用 "configure_mock()" 方法來以稍微好一點的方式為我們直接設定
回傳值：

   >>> something = Something()
   >>> mock_response = Mock(spec=open)
   >>> mock_backend = Mock()
   >>> config = {'get_endpoint.return_value.create_call.return_value.start_call.return_value': mock_response}
   >>> mock_backend.configure_mock(**config)

有了這些，我們就可以原地 (in place) monkey patch "mock backend"，並且
可以進行真正的呼叫：

   >>> something.backend = mock_backend
   >>> something.method()

藉由使用 "mock_calls"，我們可以使用單一一個斷言來檢查鍊接呼叫。一個鍊
接呼叫是一行程式碼中的多個呼叫，因此 "mock_calls" 中會有多個項目。我們
可以使用 "call.call_list()" 來為我們建立這個呼叫串列：

   >>> chained = call.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
   >>> call_list = chained.call_list()
   >>> assert mock_backend.mock_calls == call_list


部分 mocking
------------

在某些測試中，我們會想 mock 對 "datetime.date.today()" 的呼叫以回傳一
個已知日期，但我不想阻止測試中的程式碼建立新的日期物件。不幸的是
"datetime.date" 是用 C 語言寫的，所以們我不能 monkey patch 靜態的
"datetime.date.today()" 方法。

我們找到了一種簡單的方法來做到這一點，其用 mock 有效地包裝日期類別，但
將對建構函式的呼叫傳遞給真實的類別（並返回真實的實例）。

這裡使用 "patch 裝飾器" 來 mock 被測模組中的 "date" 類別。然後，mock
日期類別上的 "side_effect" 屬性會被設定為回傳真實日期的 lambda 函式。
當 mock 日期類別被呼叫時，將透過 "side_effect" 建構並回傳真實日期。：

   >>> from datetime import date
   >>> with patch('mymodule.date') as mock_date:
   ...     mock_date.today.return_value = date(2010, 10, 8)
   ...     mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
   ...
   ...     assert mymodule.date.today() == date(2010, 10, 8)
   ...     assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)

注意，我們沒有全域 patch "datetime.date"，而是在*使用*它的模組中 patch
"date"。請參閱 該 patch 何處。

當 "date.today()" 被呼叫時，一個已知日期會被回傳，但對 "date(...)" 建
構函式的呼叫仍然會回傳正常日期。如果不這樣使用，你可能會發現自己必須使
用與被測程式碼完全相同的演算法來計算預期結果，這是一個典型的測試的反面
模式 (anti-pattern)。

對日期建構函式的呼叫被記錄在 "mock_date" 屬性（"call_count" 及其相關屬
性）中，這對你的測試也可能有用處。

處理 mock 日期或其他內建類別的另一種方法在 這個 blog 中討論。


Mock 產生器方法
---------------

Python 產生器是一個函式或方法，它使用 "yield" 陳述式在疊代 [1] 時回傳
一系列的值。

產生器方法 / 函式會被呼叫以回傳產生器物件。之後此產生器會進行疊代。疊
代的協定方法是 "__iter__()"，所以我們可以使用 "MagicMock" 來 mock 它。

下面是一個範例類別，其包含實作產生器的一個 "iter" 方法：

>>> class Foo:
...     def iter(self):
...         for i in [1, 2, 3]:
...             yield i
...
>>> foo = Foo()
>>> list(foo.iter())
[1, 2, 3]

我們該如何 mock 這個類別，特別是它的 "iter" 方法呢？

要配置從疊代回傳的值（隱含在對 "list" 的呼叫中），我們需要配置呼叫
"foo.iter()" 所回傳的物件。

>>> mock_foo = MagicMock()
>>> mock_foo.iter.return_value = iter([1, 2, 3])
>>> list(mock_foo.iter())
[1, 2, 3]

[1] 還有關於產生器運算式及產生器的更多 進階用法 ，但我們在這裡不考慮它
    們。一個關於產生器及其強大功能的優良說明是：Generator Tricks for
    Systems Programmers。


對每個測試方法應用相同的 patch
------------------------------

如果你希望 patch 能用在多個測試方法上，顯而易見的方式是將 patch 裝飾器
應用於每個方法。這感覺是不必要的重複行為，因此你可以使用 "patch()"（及
其他 patch 的變體）作為類別裝飾器。這會將 patch 應用在該類別的所有測試
方法上。測試方法由名稱以 "test" 開頭來識別：

   >>> @patch('mymodule.SomeClass')
   ... class MyTest(unittest.TestCase):
   ...
   ...     def test_one(self, MockSomeClass):
   ...         self.assertIs(mymodule.SomeClass, MockSomeClass)
   ...
   ...     def test_two(self, MockSomeClass):
   ...         self.assertIs(mymodule.SomeClass, MockSomeClass)
   ...
   ...     def not_a_test(self):
   ...         return 'something'
   ...
   >>> MyTest('test_one').test_one()
   >>> MyTest('test_two').test_two()
   >>> MyTest('test_two').not_a_test()
   'something'

管理 patch 的另一種方式是使用 patch 方法：啟動與停止。這允許你將 patch
移到你的 "setUp" 與 "tearDown" 方法中。：

   >>> class MyTest(unittest.TestCase):
   ...     def setUp(self):
   ...         self.patcher = patch('mymodule.foo')
   ...         self.mock_foo = self.patcher.start()
   ...
   ...     def test_foo(self):
   ...         self.assertIs(mymodule.foo, self.mock_foo)
   ...
   ...     def tearDown(self):
   ...         self.patcher.stop()
   ...
   >>> MyTest('test_foo').run()

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

   >>> class MyTest(unittest.TestCase):
   ...     def setUp(self):
   ...         patcher = patch('mymodule.foo')
   ...         self.addCleanup(patcher.stop)
   ...         self.mock_foo = patcher.start()
   ...
   ...     def test_foo(self):
   ...         self.assertIs(mymodule.foo, self.mock_foo)
   ...
   >>> MyTest('test_foo').run()


Mock Unbound Methods （未繫結方法）
-----------------------------------

在撰寫測試時，當我們需要 patch 一個*未繫結方法*（patch 類別上的方法而
不是實例上的方法）。我們需要將 self 作為第一個引數傳入，因為我們想斷言
哪些物件正在呼叫這個特定方法。問題是你無法為此使用 mock 進行 patch，因
為就算你用一個 mock 替換未繫結方法，從實例取得它時它也不會成為一個繫結
方法，因此 self 並不會被傳遞。解決方法是使用真實的函式來 patch 未繫結
方法。"patch()" 裝飾器使得用 mock 來 patch out 方法是如此的簡單，以至
於建立一個真正的函式相對變得很麻煩。

如果你將 "autospec=True" 傳遞給 patch，那麼它會使用*真的*函式物件來進
行 patch。此函式物件與它所替換的函式物件具有相同的簽名，但實際上委託給
mock。你仍然會以與之前完全相同的方式自動建立 mock。但這意味著，如果你
使用它來 patch 類別上的未繫結方法，則從實例取得的 mock 函式將轉換為繫
結方法。"self" 會作為其第一個引數傳入，而這正是我們想要的：

>>> class Foo:
...   def foo(self):
...     pass
...
>>> with patch.object(Foo, 'foo', autospec=True) as mock_foo:
...   mock_foo.return_value = 'foo'
...   foo = Foo()
...   foo.foo()
...
'foo'
>>> mock_foo.assert_called_once_with(foo)

如果我們不使用 "autospec=True"，那麼未繫結方法將使用一個 Mock 實例
patch out，並且不被使用 "self" 進行呼叫。


使用 mock 檢查多個呼叫
----------------------

mock 有很好的 API，用於對 mock 物件的使用方式做出斷言。

>>> mock = Mock()
>>> mock.foo_bar.return_value = None
>>> mock.foo_bar('baz', spam='eggs')
>>> mock.foo_bar.assert_called_with('baz', spam='eggs')

如果你的 mock 只被呼叫一次，你可以使用 "assert_called_once_with()" 方
法，其也斷言 "call_count" 是1。

>>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')
>>> mock.foo_bar()
>>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')
Traceback (most recent call last):
    ...
AssertionError: Expected to be called once. Called 2 times.

"assert_called_with" 和 "assert_called_once_with" 都對*最近一次*的呼叫
做出斷言。如果你的 mock 將被多次呼叫，並且你想要對*所有*這些呼叫進行斷
言，你可以使用 "call_args_list"：

>>> mock = Mock(return_value=None)
>>> mock(1, 2, 3)
>>> mock(4, 5, 6)
>>> mock()
>>> mock.call_args_list
[call(1, 2, 3), call(4, 5, 6), call()]

"call" 輔助函式可以輕鬆地對這些呼叫做出斷言。你可以建立預期呼叫的清單
並將其與 "call_args_list" 進行比較。這看起來與 "call_args_list" 的
repr 非常相似：

>>> expected = [call(1, 2, 3), call(4, 5, 6), call()]
>>> mock.call_args_list == expected
True


應對可變引數
------------

另一種情況很少見，但可能會困擾你，那就是當你的 mock 被使用可變引數呼叫
。"call_args" 和 "call_args_list" 儲存對引數的*參照*。如果引數被測試中
的程式碼改變，那麼你將無法再對 mock 被呼叫時的值進行斷言。

這是一些秀出問題的程式碼範例。 想像 'mymodule' 中定義以下函式：

   def frob(val):
       pass

   def grob(val):
       "First frob and then clear val"
       frob(val)
       val.clear()

當我們嘗試測試 "grob" 使用正確的引數呼叫 "frob" 時，看看會發生什麼：

   >>> with patch('mymodule.frob') as mock_frob:
   ...     val = {6}
   ...     mymodule.grob(val)
   ...
   >>> val
   set()
   >>> mock_frob.assert_called_with({6})
   Traceback (most recent call last):
       ...
   AssertionError: Expected: (({6},), {})
   Called with: ((set(),), {})

一種可能是讓 mock 複製你傳入的引數。如果你進行的斷言依賴於物件識別性來
確定相等性，這就可能導致問題。

以下是一種使用 "side_effect" 功能的解法。如果你為 mock 提供一個
"side_effect" 函式，則 "side_effect" 將被使用與 mock 相同的引數呼叫。
這使我們有機會複製引數並將其儲存以供之後的斷言。在這個範例中，我們使用
*另一個* mock 來儲存引數，以便我們可以使用 mock 方法來執行斷言。同樣的
，有一個輔助函式為我們設定了這些。：

   >>> from copy import deepcopy
   >>> from unittest.mock import Mock, patch, DEFAULT
   >>> def copy_call_args(mock):
   ...     new_mock = Mock()
   ...     def side_effect(*args, **kwargs):
   ...         args = deepcopy(args)
   ...         kwargs = deepcopy(kwargs)
   ...         new_mock(*args, **kwargs)
   ...         return DEFAULT
   ...     mock.side_effect = side_effect
   ...     return new_mock
   ...
   >>> with patch('mymodule.frob') as mock_frob:
   ...     new_mock = copy_call_args(mock_frob)
   ...     val = {6}
   ...     mymodule.grob(val)
   ...
   >>> new_mock.assert_called_with({6})
   >>> new_mock.call_args
   call({6})

"copy_call_args" 與將要被呼叫的 mock 一起被呼叫。它回傳一個我們會對其
進行斷言的新的 mock。"side_effect" 函式建立引數們的副本，並用該副本呼
叫我們的 "new_mock"。

備註:

  如果你的 mock 只會被使用一次，則有一種更簡單的方法可以在呼叫引數時檢
  查它們。你可以簡單地在 "side_effect" 函式內進行檢查。

  >>> def side_effect(arg):
  ...     assert arg == {6}
  ...
  >>> mock = Mock(side_effect=side_effect)
  >>> mock({6})
  >>> mock(set())
  Traceback (most recent call last):
      ...
  AssertionError

另一種方法是建立 "Mock" 或 "MagicMock" 的子類別來複製（使用
"copy.deepcopy()"）引數。這是一個實作的例子：

>>> from copy import deepcopy
>>> class CopyingMock(MagicMock):
...     def __call__(self, /, *args, **kwargs):
...         args = deepcopy(args)
...         kwargs = deepcopy(kwargs)
...         return super().__call__(*args, **kwargs)
...
>>> c = CopyingMock(return_value=None)
>>> arg = set()
>>> c(arg)
>>> arg.add(1)
>>> c.assert_called_with(set())
>>> c.assert_called_with(arg)
Traceback (most recent call last):
    ...
AssertionError: Expected call: mock({1})
Actual call: mock(set())
>>> c.foo
<CopyingMock name='mock.foo' id='...'>

當你將 "Mock" 或 "MagicMock" 子類別化時，所有屬性會被動態建立，且
"return_value" 會自動使用你的子類別。這代表著 "CopyingMock" 的所有子代
(child) 也會具有 "CopyingMock" 型別。


巢狀使用 Patch
--------------

將 patch 作為情境管理器使用很好，但是如果你使用複數個 patch，你最終可
能會得到巢狀的 with 陳述式，並且越來越向右縮排：

   >>> class MyTest(unittest.TestCase):
   ...
   ...     def test_foo(self):
   ...         with patch('mymodule.Foo') as mock_foo:
   ...             with patch('mymodule.Bar') as mock_bar:
   ...                 with patch('mymodule.Spam') as mock_spam:
   ...                     assert mymodule.Foo is mock_foo
   ...                     assert mymodule.Bar is mock_bar
   ...                     assert mymodule.Spam is mock_spam
   ...
   >>> original = mymodule.Foo
   >>> MyTest('test_foo').test_foo()
   >>> assert mymodule.Foo is original

我們可以使用 unittest 的 "cleanup" 函式以及 patch 方法：啟動與停止 來
達到相同的效果，而不會出現因巢狀導致的縮排。一個簡單的輔助方法
"create_patch" 會將 patch 放置到位並為我們回傳被建立的 mock：

   >>> class MyTest(unittest.TestCase):
   ...
   ...     def create_patch(self, name):
   ...         patcher = patch(name)
   ...         thing = patcher.start()
   ...         self.addCleanup(patcher.stop)
   ...         return thing
   ...
   ...     def test_foo(self):
   ...         mock_foo = self.create_patch('mymodule.Foo')
   ...         mock_bar = self.create_patch('mymodule.Bar')
   ...         mock_spam = self.create_patch('mymodule.Spam')
   ...
   ...         assert mymodule.Foo is mock_foo
   ...         assert mymodule.Bar is mock_bar
   ...         assert mymodule.Spam is mock_spam
   ...
   >>> original = mymodule.Foo
   >>> MyTest('test_foo').run()
   >>> assert mymodule.Foo is original


使用 MagicMock 來 mock 字典
---------------------------

你可能會想要 mock 字典或其他容器物件，記錄對它的所有存取，同時讓它仍然
像字典一樣運作。

我們可以使用 "MagicMock" 來做到這一點，它的行為會與字典一致，並使用
"side_effect" 將字典存取委託給我們控制下的真實底層字典。

當 "MagicMock" 的 "__getitem__()" 和 "__setitem__()" 方法被呼叫時（一
般的字典存取）， "side_effect" 會被使用鍵 (key) 來呼叫 （在
"__setitem__" 的情況也會使用值 (value)）。我們也可以控制回傳的內容。

使用 "MagicMock" 後，我們可以使用諸如 "call_args_list" 之類的屬性來斷
言字典被使用的方式：

>>> my_dict = {'a': 1, 'b': 2, 'c': 3}
>>> def getitem(name):
...      return my_dict[name]
...
>>> def setitem(name, val):
...     my_dict[name] = val
...
>>> mock = MagicMock()
>>> mock.__getitem__.side_effect = getitem
>>> mock.__setitem__.side_effect = setitem

備註:

  不使用 "MagicMock" 的替代方案是使用 "Mock" 並且*只*提供你特別想要的
  魔術方法：

  >>> mock = Mock()
  >>> mock.__getitem__ = Mock(side_effect=getitem)
  >>> mock.__setitem__ = Mock(side_effect=setitem)

  *第三個*選擇是使用 "MagicMock"，但傳入 "dict" 作為 *spec* （或
  *spec_set*）引數，以使被建立的 "MagicMock" 僅具有字典可用的魔術方法
  ：

  >>> mock = MagicMock(spec_set=dict)
  >>> mock.__getitem__.side_effect = getitem
  >>> mock.__setitem__.side_effect = setitem

有了這些 side effect 函式，"mock" 會像一般的字典一樣運作，但會記錄存取
。如果你嘗試存取不存在的鍵，它甚至會引發一個 "KeyError"。

>>> mock['a']
1
>>> mock['c']
3
>>> mock['d']
Traceback (most recent call last):
    ...
KeyError: 'd'
>>> mock['b'] = 'fish'
>>> mock['d'] = 'eggs'
>>> mock['b']
'fish'
>>> mock['d']
'eggs'

在上述方式被使用後，你就可以使用普通的 mock 方法和屬性對存取進行斷言：

>>> mock.__getitem__.call_args_list
[call('a'), call('c'), call('d'), call('b'), call('d')]
>>> mock.__setitem__.call_args_list
[call('b', 'fish'), call('d', 'eggs')]
>>> my_dict
{'a': 1, 'b': 'fish', 'c': 3, 'd': 'eggs'}


Mock 子類別及其屬性
-------------------

你會想子類別化 "Mock" 的原因可能有很多種。其中之一是增加輔助方法。以下
是一個有點笨的例子：

>>> class MyMock(MagicMock):
...     def has_been_called(self):
...         return self.called
...
>>> mymock = MyMock(return_value=None)
>>> mymock
<MyMock id='...'>
>>> mymock.has_been_called()
False
>>> mymock()
>>> mymock.has_been_called()
True

"Mock" 實例的標準行為是屬性 mock 和回傳值 mock 會與存取它們的 mock 具
有相同的型別。這確保了 "Mock" 屬性是 "Mocks"，"MagicMock" 屬性是
"MagicMocks" [2]。因此，如果你要子類別化以新增輔助方法，那麼它們也可用
於子類別實例的屬性 mock 和回傳值 mock。

>>> mymock.foo
<MyMock name='mock.foo' id='...'>
>>> mymock.foo.has_been_called()
False
>>> mymock.foo()
<MyMock name='mock.foo()' id='...'>
>>> mymock.foo.has_been_called()
True

有時候這很不方便。例如，有一個使用者正在子類別化 mock 以建立 Twisted
adaptor。將其應用於屬性實際上會導致錯誤。

"Mock" （及其各種形式）使用名為 "_get_child_mock" 的方法來為屬性和回傳
值建立這些 "子 mock"。你可以透過置換此方法來防止你的子類別被用為屬性。
其簽名是取用任意的關鍵字引數（"**kwargs"），然後將其傳遞給 mock 建構函
式：

>>> class Subclass(MagicMock):
...     def _get_child_mock(self, /, **kwargs):
...         return MagicMock(**kwargs)
...
>>> mymock = Subclass()
>>> mymock.foo
<MagicMock name='mock.foo' id='...'>
>>> assert isinstance(mymock, Subclass)
>>> assert not isinstance(mymock.foo, Subclass)
>>> assert not isinstance(mymock(), Subclass)

[2] 此規則的例外是非可呼叫物件的 mock。屬性使用可呼叫物件的變體，否則
    非可呼叫物件的 mock 無法具有可呼叫的方法。


使用 patch.dict 來 mock import
------------------------------

可能會讓 mock 很困難的一種情況是在函式內部進行區域 import。這些狀況會
更難進行 mock，因為它們沒有使用我們可以 patch out 的模組命名空間中的物
件。

一般來說，我們應該避免區域 import 的發生。有時這樣做是為了防止循環相依
(circular dependencies)，為此*通常*有更好的方式來解決問題（例如重構程
式碼）或透過延遲 import 來防止「前期成本 (up front costs)」。這也可以
透過比無條件的區域 import 更好的方式來解決（例如將模組儲存為類別或模組
屬性，並且僅在第一次使用時進行 import）。

除此之外，還有一種方法可以使用 "mock" 來影響 import 的結果。Import 會
從 "sys.modules" 字典中取得一個*物件*。請注意，它會取得一個*物件*，而
該物件不需要是一個模組。初次 import 模組會導致模組物件被放入
"sys.modules" 中，因此通常當你 import 某些東西時，你會得到一個模組。但
並非一定要如此。

這代表你可以使用 "patch.dict()" 來*暫時*在 "sys.modules" 中原地放置
mock。在這個 patch 作用時的任何 import 都會取得這個 mock。當 patch 完
成時（被裝飾的函式結束、with 陳述式主體完成或 "patcher.stop()" 被呼叫
），那麼之前在 "sys.modules" 中的任何內容都會被安全地復原。

下面是一個 mock out 'fooble' 模組的例子。

>>> import sys
>>> mock = Mock()
>>> with patch.dict('sys.modules', {'fooble': mock}):
...    import fooble
...    fooble.blob()
...
<Mock name='mock.blob()' id='...'>
>>> assert 'fooble' not in sys.modules
>>> mock.blob.assert_called_once_with()

如你所見，"import fooble" 成功了，但在離開之後，"sys.modules" 中就沒有
'fooble' 了。

這也適用於 "from module import name" 形式：

>>> mock = Mock()
>>> with patch.dict('sys.modules', {'fooble': mock}):
...    from fooble import blob
...    blob.blip()
...
<Mock name='mock.blob.blip()' id='...'>
>>> mock.blob.blip.assert_called_once_with()

透過稍微多一點的處理，你也可以 mock 套件的引入：

>>> mock = Mock()
>>> modules = {'package': mock, 'package.module': mock.module}
>>> with patch.dict('sys.modules', modules):
...    from package.module import fooble
...    fooble()
...
<Mock name='mock.module.fooble()' id='...'>
>>> mock.module.fooble.assert_called_once_with()


追蹤呼叫順序與更簡潔的呼叫斷言
------------------------------

"Mock" 類別可以讓你透過 "method_calls" 屬性追蹤 mock 物件上方法呼叫的*
順序*。這不會讓你可以追蹤不同的 mock 物件之間的呼叫順序，然而我們可以
使用 "mock_calls" 來達到相同的效果。

因為 mock 會在 "mock_calls" 中追蹤對子 mock 的呼叫，且存取 mock 的任意
屬性會建立一個子 mock，所以我們可以從父 mock 建立不同的 mock。之後對這
些子 mock 的呼叫將依序全部記錄在父 mock 的 "mock_calls" 中：

>>> manager = Mock()
>>> mock_foo = manager.foo
>>> mock_bar = manager.bar

>>> mock_foo.something()
<Mock name='mock.foo.something()' id='...'>
>>> mock_bar.other.thing()
<Mock name='mock.bar.other.thing()' id='...'>

>>> manager.mock_calls
[call.foo.something(), call.bar.other.thing()]

之後我們可以透過與管理器 (manager) mock 上的 "mock_calls" 屬性進行比較
來斷言呼叫及其順序：

>>> expected_calls = [call.foo.something(), call.bar.other.thing()]
>>> manager.mock_calls == expected_calls
True

如果 "patch" 正在被建立並放置為你的 mock，那麼你可以使用
"attach_mock()" 方法將它們附加到管理器 mock。附加之後，呼叫將被記錄在
管理器的 "mock_calls" 中。：

   >>> manager = MagicMock()
   >>> with patch('mymodule.Class1') as MockClass1:
   ...     with patch('mymodule.Class2') as MockClass2:
   ...         manager.attach_mock(MockClass1, 'MockClass1')
   ...         manager.attach_mock(MockClass2, 'MockClass2')
   ...         MockClass1().foo()
   ...         MockClass2().bar()
   <MagicMock name='mock.MockClass1().foo()' id='...'>
   <MagicMock name='mock.MockClass2().bar()' id='...'>
   >>> manager.mock_calls
   [call.MockClass1(),
   call.MockClass1().foo(),
   call.MockClass2(),
   call.MockClass2().bar()]

如果進行了多次呼叫，但你只對其中特定呼叫的序列感興趣，則可以使用
"assert_has_calls()" 方法。這需要一個呼叫串列（使用 "call" 物件建構）
。如果該呼叫序列位於 "mock_calls" 中，則斷言成功。

>>> m = MagicMock()
>>> m().foo().bar().baz()
<MagicMock name='mock().foo().bar().baz()' id='...'>
>>> m.one().two().three()
<MagicMock name='mock.one().two().three()' id='...'>
>>> calls = call.one().two().three().call_list()
>>> m.assert_has_calls(calls)

儘管鍊接呼叫 "m.one().two().three()" 並不是對 mock 進行的唯一呼叫，斷
言仍會成功。

有時可能會對一個 mock 進行多次呼叫，而你只對斷言其中*某些*呼叫感興趣。
你甚至可能不關心順序。在這種情況下，你可以將 "any_order=True" 傳遞給
"assert_has_calls"：

>>> m = MagicMock()
>>> m(1), m.two(2, 3), m.seven(7), m.fifty('50')
(...)
>>> calls = [call.fifty('50'), call(1), call.seven(7)]
>>> m.assert_has_calls(calls, any_order=True)


更複雜的引數匹配
----------------

使用與 "ANY" 相同的基本概念，我們可以實作匹配器 (matcher)，對用來作為
mock 引數的物件進行更複雜的斷言。

假設我們預期某個物件會被傳進 mock，預設情況下，該 mock 會根據物件識別
性進行相等比較（對使用者定義類別的 Python 預設行為）。要使用
"assert_called_with()"，我們需要傳入完全相同的物件。如果我們只對該物件
的某些屬性感興趣，那麼我們可以建立一個匹配器來為我們檢查這些屬性。

你可以在這個範例中看到對 "assert_called_with" 的一個「標準」呼叫是不夠
的：

>>> class Foo:
...     def __init__(self, a, b):
...         self.a, self.b = a, b
...
>>> mock = Mock(return_value=None)
>>> mock(Foo(1, 2))
>>> mock.assert_called_with(Foo(1, 2))
Traceback (most recent call last):
    ...
AssertionError: Expected: call(<__main__.Foo object at 0x...>)
Actual call: call(<__main__.Foo object at 0x...>)

對我們的 "Foo" 類別的比較函式看起來可能會像這樣：

>>> def compare(self, other):
...     if not type(self) == type(other):
...         return False
...     if self.a != other.a:
...         return False
...     if self.b != other.b:
...         return False
...     return True
...

而可以使用像這樣的比較函式進行其相等性運算的匹配器物件會長得像這樣：

>>> class Matcher:
...     def __init__(self, compare, some_obj):
...         self.compare = compare
...         self.some_obj = some_obj
...     def __eq__(self, other):
...         return self.compare(self.some_obj, other)
...

把這些都放在一起：

>>> match_foo = Matcher(compare, Foo(1, 2))
>>> mock.assert_called_with(match_foo)

"Matcher" 是用我們想要比較的 "Foo" 物件和比較函式實例化的。在
"assert_called_with" 中，"Matcher" 相等方法將被呼叫，它將呼叫 mock 時
傳入的物件與我們建立匹配器時傳入的物件進行比較。如果它們匹配，則
"assert_called_with" 會通過，如果它們不匹配，則會引發 "AssertionError"
：

>>> match_wrong = Matcher(compare, Foo(3, 4))
>>> mock.assert_called_with(match_wrong)
Traceback (most recent call last):
    ...
AssertionError: Expected: ((<Matcher object at 0x...>,), {})
Called with: ((<Foo object at 0x...>,), {})

透過一些調整，你可以讓比較函式直接引發 "AssertionError" 並提供更有用的
失敗訊息。

從版本 1.5 開始，Python 測試函式庫 PyHamcrest 以其相等匹配器的形式，提
供了類似的功能，在這裡可能是有用的
(hamcrest.library.integration.match_equality)。
