新聞中心
unittest.mock —- 上手指南
3.3 新版功能.

我們提供的服務(wù)有:做網(wǎng)站、成都網(wǎng)站建設(shè)、微信公眾號(hào)開(kāi)發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、水城ssl等。為超過(guò)千家企事業(yè)單位解決了網(wǎng)站和推廣的問(wèn)題。提供周到的售前咨詢(xún)和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的水城網(wǎng)站制作公司
使用 mock
模擬方法調(diào)用
使用 Mock 的常見(jiàn)場(chǎng)景:
模擬函數(shù)調(diào)用
記錄在對(duì)象上的方法調(diào)用
你可能需要替換一個(gè)對(duì)象上的方法,用于確認(rèn)此方法被系統(tǒng)中的其他部分調(diào)用過(guò),并且調(diào)用時(shí)使用了正確的參數(shù)。
>>> real = SomeClass()>>> real.method = MagicMock(name='method')>>> real.method(3, 4, 5, key='value')
使用了 mock (本例中的 real.method) 之后,它有方法和屬性可以讓你針對(duì)它是被如何使用的下斷言。
備注
在多數(shù)示例中,Mock 與 MagicMock 兩個(gè)類(lèi)可以相互替換,而 MagicMock 是一個(gè)更適用的類(lèi),通常情況下,使用它就可以了。
如果 mock 被調(diào)用,它的 called 屬性就會(huì)變成 True,更重要的是,我們可以使用 assert_called_with() 或者 assert_called_once_with() 方法來(lái)確認(rèn)它在被調(diào)用時(shí)使用了正確的參數(shù)。
在如下的測(cè)試示例中,驗(yàn)證對(duì)于 ProductionClass().method 的調(diào)用會(huì)導(dǎo)致 something 的調(diào)用。
>>> 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)
對(duì)象上的方法調(diào)用的 mock
上一個(gè)例子中我們直接在對(duì)象上給方法打補(bǔ)丁以檢查它是否被正確地調(diào)用。 另一個(gè)常見(jiàn)的用例是將一個(gè)對(duì)象傳給一個(gè)方法(或被測(cè)試系統(tǒng)的某個(gè)部分)然后檢查它是否以正確的方式被使用。
下面這個(gè)簡(jiǎn)單的 ProductionClass 具有一個(gè) closer 方法。 如果它附帶一個(gè)對(duì)象被調(diào)用那么它就會(huì)調(diào)用其中的 close。
>>> class ProductionClass:... def closer(self, something):... something.close()...
所以為了測(cè)試它我們需要傳入一個(gè)帶有 close 方法的對(duì)象并檢查它是否被正確地調(diào)用。
>>> real = ProductionClass()>>> mock = Mock()>>> real.closer(mock)>>> mock.close.assert_called_with()
我們不需要做任何事來(lái)在我們的 mock 上提供 ‘close’ 方法。 訪問(wèn) close 的操作就會(huì)創(chuàng)建它。 因此,如果 ‘close’ 還未被調(diào)用那么在測(cè)試時(shí)訪問(wèn)它就將創(chuàng)建它,但是 assert_called_with() 則會(huì)引發(fā)一個(gè)失敗的異常。
模擬類(lèi)
一個(gè)常見(jiàn)的用例是模擬被測(cè)試的代碼所實(shí)例化的類(lèi)。 當(dāng)你給一個(gè)類(lèi)打上補(bǔ)丁,該類(lèi)就會(huì)被替換為一個(gè) mock。 實(shí)例是通過(guò) 該用該類(lèi) 來(lái)創(chuàng)建的。 這意味著你要通過(guò)查看被模擬類(lèi)的返回值來(lái)訪問(wèn) “mock 實(shí)例”。
在下面的例子中我們有一個(gè)函數(shù) some_function 實(shí)例化了 Foo 并調(diào)用該實(shí)例中的一個(gè)方法。 對(duì) patch() 的調(diào)用會(huì)將類(lèi) Foo 替換為一個(gè) mock。 Foo 實(shí)例是調(diào)用該 mock 的結(jié)果,所以它是通過(guò)修改 return_value 來(lái)配置的。
>>> 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 起個(gè)名字可能會(huì)很有用。 名字會(huì)顯示在 mock 的 repr 中并在 mock 出現(xiàn)于測(cè)試失敗消息中時(shí)可以幫助理解。 這個(gè)名字也會(huì)被傳播給 mock 的屬性或方法:
>>> mock = MagicMock(name='foo')>>> mock>>> mock.method
追蹤所有的調(diào)用
通常你會(huì)想要追蹤對(duì)某個(gè)方法的多次調(diào)用。 mock_calls 屬性記錄了所有對(duì) mock 的子屬性的調(diào)用 —— 并且還包括對(duì)它們的子屬性的調(diào)用。
>>> mock = MagicMock()>>> mock.method()>>> mock.attribute.method(10, x=53)>>> mock.mock_calls[call.method(), call.attribute.method(10, x=53)]
如果你做了一個(gè)有關(guān) mock_calls 的斷言并且有任何非預(yù)期的方法被調(diào)用,則斷言將失敗。 這很有用處,因?yàn)槌藬嘌阅闼A(yù)期的調(diào)用已被執(zhí)行,你還會(huì)檢查它們是否以正確的順序被執(zhí)行并且沒(méi)有額外的調(diào)用:
你使用 call 對(duì)象來(lái)構(gòu)造列表以便與 mock_calls 進(jìn)行比較:
>>> expected = [call.method(), call.attribute.method(10, x=53)]>>> mock.mock_calls == expectedTrue
然而,返回 mock 的調(diào)用的形參不會(huì)被記錄,這意味著不可能追蹤附帶了重要形參的創(chuàng)建上級(jí)對(duì)象的嵌套調(diào)用:
>>> m = Mock()>>> m.factory(important=True).deliver()>>> m.mock_calls[-1] == call.factory(important=False).deliver()True
設(shè)置返回值和屬性
在 mock 對(duì)象上設(shè)置返回值是非常容易的:
>>> mock = Mock()>>> mock.return_value = 3>>> mock()3
當(dāng)然你也可以對(duì) mock 上的方法做同樣的操作:
>>> mock = Mock()>>> mock.method.return_value = 3>>> mock.method()3
返回值也可以在構(gòu)造器中設(shè)置:
>>> mock = Mock(return_value=3)>>> mock()3
如果你需要在你的 mock 上設(shè)置一個(gè)屬性,只需這樣做:
>>> mock = Mock()>>> mock.x = 3>>> mock.x3
有時(shí)你會(huì)想要模擬更復(fù)雜的情況,例如這個(gè)例子 mock.connection.cursor().execute("SELECT 1")。 如果我們希望這個(gè)調(diào)用返回一個(gè)列表,那么我們還必須配置嵌套調(diào)用的結(jié)果。
我們可以像這樣使用 call 在一個(gè)“鏈?zhǔn)秸{(diào)用”中構(gòu)造調(diào)用集合以便隨后方便地設(shè)置斷言:
>>> 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 == expectedTrue
對(duì) .call_list() 的調(diào)用會(huì)將我們的調(diào)用對(duì)象轉(zhuǎn)成一個(gè)代表鏈?zhǔn)秸{(diào)用的調(diào)用列表。
通過(guò) mock 引發(fā)異常
一個(gè)很有用的屬性是 side_effect。 如果你將該屬性設(shè)為一個(gè)異常類(lèi)或者實(shí)例那么當(dāng) mock 被調(diào)用時(shí)該異常將會(huì)被引發(fā)。
>>> mock = Mock(side_effect=Exception('Boom!'))>>> mock()Traceback (most recent call last):...Exception: Boom!
附帶影響函數(shù)和可迭代對(duì)象
side_effect 也可以被設(shè)為一個(gè)函數(shù)或可迭代對(duì)象。 side_effect 作為可迭代對(duì)象的應(yīng)用場(chǎng)景適用于你的 mock 將要被多次調(diào)用,并且你希望每次調(diào)用都返回不同的值的情況。 當(dāng)你將 side_effect 設(shè)為一個(gè)可迭代對(duì)象時(shí)每次對(duì) mock 的調(diào)用將返回可迭代對(duì)象的下一個(gè)值。
>>> mock = MagicMock(side_effect=[4, 5, 6])>>> mock()4>>> mock()5>>> mock()6
對(duì)于更高級(jí)的用例,例如根據(jù) mock 調(diào)用時(shí)附帶的參數(shù)動(dòng)態(tài)改變返回值,side_effect 可以指定一個(gè)函數(shù)。 該函數(shù)將附帶與 mock 相同的參數(shù)被調(diào)用。 該函數(shù)所返回的就是調(diào)用所返回的對(duì)象:
>>> 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
模擬異步迭代器
從 python 3.8 起,AsyncMock 和 MagicMock 支持通過(guò) __aiter__ 來(lái)模擬 異步迭代器。 __aiter__ 的 return_value 屬性可以被用來(lái)設(shè)置要用于迭代的返回值。
>>> 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]
模擬異步上下文管理器
從 Python 3.8 起,AsyncMock 和 MagicMock 支持通過(guò) __aenter__ 和 __aexit__ 來(lái)模擬 異步上下文管理器。 在默認(rèn)情況下,__aenter__ 和 __aexit__ 將為返回異步函數(shù)的 AsyncMock 實(shí)例。
>>> 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()
基于現(xiàn)有對(duì)象創(chuàng)建模擬對(duì)象
使用模擬操作的一個(gè)問(wèn)題是它會(huì)將你的測(cè)試與你的 mock 實(shí)現(xiàn)相關(guān)聯(lián)而不是與你的真實(shí)代碼相關(guān)聯(lián)。 假設(shè)你有一個(gè)實(shí)現(xiàn)了 some_method 的類(lèi)。 在對(duì)另一個(gè)類(lèi)的測(cè)試中,你提供了一個(gè) 同樣 提供了 some_method 的模擬該對(duì)象的 mock 對(duì)象。 如果后來(lái)你重構(gòu)了第一個(gè)類(lèi),使得它不再具有 some_method —— 那么你的測(cè)試將繼續(xù)保持通過(guò),盡管現(xiàn)在你的代碼已經(jīng)被破壞了!
Mock 允許你使用allows you to provide an object as a specification for the mock, using the spec 關(guān)鍵字參數(shù)來(lái)提供一個(gè)對(duì)象作為 mock 的規(guī)格說(shuō)明。 在 mock 上訪問(wèn)不存在于你的規(guī)格說(shuō)明對(duì)象中的方法 / 屬性將立即引發(fā)一個(gè)屬性錯(cuò)誤。 如果你修改你的規(guī)格說(shuō)明的實(shí)現(xiàn),,那么使用了該類(lèi)的測(cè)試將立即開(kāi)始失敗而不需要你在這些測(cè)試中實(shí)例化該類(lèi)。
>>> mock = Mock(spec=SomeClass)>>> mock.old_method()Traceback (most recent call last):...AttributeError: object has no attribute 'old_method'
使用規(guī)格說(shuō)明還可以啟用對(duì) mock 的調(diào)用的更聰明的匹配操作,無(wú)論是否有將某些形參作為位置或關(guān)鍵字參數(shù)傳入:
>>> def f(a, b, c): pass...>>> mock = Mock(spec=f)>>> mock(1, 2, 3)>>> mock.assert_called_with(a=1, b=2, c=3)
如果你想要讓這些更聰明的匹配操作也適用于 mock 上的方法調(diào)用,你可以使用 auto-speccing。
如果你想要更強(qiáng)形式的規(guī)格說(shuō)明以防止設(shè)置任意屬性并獲取它們那么你可以使用 spec_set 來(lái)代替 spec。
補(bǔ)丁裝飾器
備注
在查找對(duì)象的名稱(chēng)空間中修補(bǔ)對(duì)象使用 patch() 。使用起來(lái)很簡(jiǎn)單,閱讀 補(bǔ)丁的位置 來(lái)快速上手。
測(cè)試中的一個(gè)常見(jiàn)需求是為類(lèi)屬性或模塊屬性打補(bǔ)丁,例如修補(bǔ)內(nèi)置對(duì)象或修補(bǔ)某個(gè)模塊中的類(lèi)來(lái)測(cè)試其是否被實(shí)例化。 模塊和類(lèi)都可算是全局對(duì)象,因此對(duì)它們打補(bǔ)丁的操作必須在測(cè)試完成之后被還原否則補(bǔ)丁將持續(xù)影響其他測(cè)試并導(dǎo)致難以診斷的問(wèn)題。
為此 mock 提供了三個(gè)便捷的裝飾器: patch(), patch.object() 和 patch.dict()。 patch 接受單個(gè)字符串,其形式 package.module.Class.attribute 指明你要修補(bǔ)的屬性。 它還可選擇接受一個(gè)值用來(lái)替換指定的屬性(或者類(lèi)對(duì)象等等)。 ‘patch.object’ 接受一個(gè)對(duì)象和你想要修補(bǔ)的屬性名稱(chēng),并可選擇接受要用作補(bǔ)丁的值。
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()
如果你要給一個(gè)模塊 (包括 builtins) 打補(bǔ)丁則可使用 patch() 來(lái)代替 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"
如有必要模塊名可以是“帶點(diǎn)號(hào)”的,其形式如 package.module:
>>> @patch('package.module.ClassName.attribute', sentinel.attribute)... def test():... from package.module import ClassName... assert ClassName.attribute == sentinel.attribute...>>> test()
一個(gè)良好的模式是實(shí)際地裝飾測(cè)試方法本身:
>>> 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
如果你想要通過(guò) Mock 來(lái)打補(bǔ)丁,你可以只附帶一個(gè)參數(shù)使用 patch() (或附帶兩個(gè)參數(shù)使用 patch.object())。 這將為你創(chuàng)建 mock 并傳遞給測(cè)試函數(shù) / 方法:
>>> 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()
你可以使用以下模式來(lái)堆疊多個(gè)補(bǔ)丁裝飾器:
>>> 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()
當(dāng)你嵌套 patch 裝飾器時(shí)將以它們被應(yīng)用的相同順序(即 Python 應(yīng)用裝飾器的正常順序)將 mock 傳入被裝飾的函數(shù)。 也就是說(shuō)從下往上,因此在上面的示例中 test_module.ClassName2 的 mock 會(huì)被最先傳入。
還有一個(gè) patch.dict() 用于在一定范圍內(nèi)設(shè)置字典中的值,并在測(cè)試結(jié)束時(shí)將字典恢復(fù)為其原始狀態(tài):
>>> 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() 為你創(chuàng)建 mock 時(shí),你可以使用 with 語(yǔ)句的 “as” 形式來(lái)獲得對(duì) 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 可以被用作類(lèi)裝飾器。 當(dāng)以此方式使用時(shí)其效果與將裝飾器單獨(dú)應(yīng)用到每個(gè)以 “test” 打頭的方法上相同。
更多示例
下面是一些針對(duì)更為高級(jí)應(yīng)用場(chǎng)景的補(bǔ)充示例。
模擬鏈?zhǔn)秸{(diào)用
實(shí)際上一旦你理解了 return_value 屬性那么使用 mock 模擬鏈?zhǔn)秸{(diào)用就會(huì)相當(dāng)直觀。 當(dāng)一個(gè) mock 首次被調(diào)用,或者當(dāng)你在它被調(diào)用前獲取其 return_value 時(shí),將會(huì)創(chuàng)建一個(gè)新的 Mock。
這意味著你可以通過(guò)檢視 return_value mock 來(lái)了解從調(diào)用被模擬對(duì)象返回的對(duì)象是如何被使用的:
>>> mock = Mock()>>> mock().foo(a=2, b=3)>>> mock.return_value.foo.assert_called_with(a=2, b=3)
從這里開(kāi)始只需一個(gè)步驟即可配置并創(chuàng)建有關(guān)鏈?zhǔn)秸{(diào)用的斷言。 當(dāng)然還有另一種選擇是首先以更易于測(cè)試的方式來(lái)編寫(xiě)你的代碼…
因此,如果我們有這樣一些代碼:
>>> 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 已經(jīng)過(guò)良好測(cè)試,我們要如何測(cè)試 method()? 特別地,我們希望測(cè)試代碼段 # more code 是否以正確的方式使用了響應(yīng)對(duì)象。
由于這個(gè)鏈?zhǔn)秸{(diào)用來(lái)自一個(gè)實(shí)例屬性我們可以對(duì) backend 屬性在 Something 實(shí)例上進(jìn)行猴子式修補(bǔ)。 在這個(gè)特定情況下我們只對(duì)最后調(diào)用 start_call 的返回值感興趣所以我們不需要進(jìn)行太多的配置。 讓我們假定它返回的是“文件類(lèi)”對(duì)象,因此我們將確保我們的響應(yīng)對(duì)象使用內(nèi)置的 open() 作為其 spec。
為了做到這一點(diǎn)我們創(chuàng)建一個(gè) mock 實(shí)例作為我們的 mock 后端并為它創(chuàng)建一個(gè) mock 響應(yīng)對(duì)象。 要將該響應(yīng)對(duì)象設(shè)為最后的 start_call 的返回值我們可以這樣做:
mock_backend.get_endpoint.return_value.create_call.return_value.start_call.return_value = mock_response
我們可以通過(guò)更好一些的方式做到這一點(diǎn),即使用 configure_mock() 方法直接為我們?cè)O(shè)置返回值:
>>> 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)
有了這些我們就能準(zhǔn)備好給“mock 后端”打上猴子補(bǔ)丁并可以執(zhí)行真正的調(diào)用:
>>> something.backend = mock_backend>>> something.method()
使用 mock_calls 我們可以通過(guò)一個(gè)斷言來(lái)檢查鏈?zhǔn)秸{(diào)用。 一個(gè)鏈?zhǔn)秸{(diào)用就是在一行代碼中連續(xù)執(zhí)行多個(gè)調(diào)用,所以在 mock_calls 中將會(huì)有多個(gè)條目。 我們可以使用 call.call_list() 來(lái)為我們創(chuàng)建這個(gè)調(diào)用列表:
>>> chained = call.get_endpoint('foobar').create_call('spam', 'eggs').start_call()>>> call_list = chained.call_list()>>> assert mock_backend.mock_calls == call_list
部分模擬
在某些測(cè)試中我希望模擬對(duì) datetime.date.today() 的調(diào)用以返回一個(gè)已知的日期,但我又不想阻止被測(cè)試的代碼創(chuàng)建新的日期對(duì)象。 很不幸 datetime.date 是用 C 語(yǔ)言編寫(xiě)的,因此我不能簡(jiǎn)單地給靜態(tài)的 date.today() 方法打上猴子補(bǔ)丁。
我找到了實(shí)現(xiàn)這一點(diǎn)的簡(jiǎn)單方式即通過(guò)一個(gè) mock 來(lái)實(shí)際包裝日期類(lèi),但通過(guò)對(duì)構(gòu)造器的調(diào)用傳遞給真實(shí)的類(lèi)(并返回真實(shí)的實(shí)例)。
這里使用 patch 裝飾器 來(lái)模擬被測(cè)試模塊中的 date 類(lèi)。 模擬 date 類(lèi)中的 side_effect 屬性隨后被設(shè)為一個(gè)返回真實(shí)日期的 lambda 函數(shù)。 當(dāng)模擬 date 類(lèi)被調(diào)用時(shí)將由 side_effect 構(gòu)造并返回一個(gè)真實(shí)日期。
>>> 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)
請(qǐng)注意我們沒(méi)有在全局范圍上修補(bǔ) datetime.date,我們只是在 使用 它的模塊中給 date 打補(bǔ)丁。 參見(jiàn) 補(bǔ)丁的位置。
當(dāng) date.today() 被調(diào)用時(shí)將返回一個(gè)已知的日期,但對(duì) date(...) 構(gòu)造器的調(diào)用仍會(huì)返回普通的日期。 如果不是這樣你會(huì)發(fā)現(xiàn)你必須使用與被測(cè)試的代碼完全相同的算法來(lái)計(jì)算出預(yù)期的結(jié)果,這是測(cè)試工作中的一個(gè)經(jīng)典的反模式。
對(duì) date 構(gòu)造器的調(diào)用會(huì)被記錄在 mock_date 屬性中 (call_count 等),它們也可能對(duì)你的測(cè)試有用處。
有關(guān)處理模塊日期或其他內(nèi)置類(lèi)的一種替代方式的討論請(qǐng)參見(jiàn) 這篇博客文章。
模擬生成器方法
Python 生成器是指在被迭代時(shí)使用 yield 語(yǔ)句來(lái)返回一系列值的函數(shù)或方法 1。
調(diào)用生成器方法 / 函數(shù)將返回生成器對(duì)象。 生成器對(duì)象隨后會(huì)被迭代。 迭代操作對(duì)應(yīng)的協(xié)議方法是 __iter__(),因此我們可以使用 MagicMock 來(lái)模擬它。
以下是一個(gè)使用 “iter” 方法模擬為生成器的示例類(lèi):
>>> class Foo:... def iter(self):... for i in [1, 2, 3]:... yield i...>>> foo = Foo()>>> list(foo.iter())[1, 2, 3]
我們要如何模擬這個(gè)類(lèi),特別是它的 “iter” 方法呢?
為了配置從迭代操作(隱含在對(duì) list 的調(diào)用中)返回的值,我們需要配置調(diào)用 foo.iter() 所返回的對(duì)象。
>>> mock_foo = MagicMock()>>> mock_foo.iter.return_value = iter([1, 2, 3])>>> list(mock_foo.iter())[1, 2, 3]
1
此外還有生成器表達(dá)式和更多的生成器 進(jìn)階用法,但在這里我們不去關(guān)心它們。 有關(guān)生成器及其強(qiáng)大功能的一個(gè)很好的介紹請(qǐng)參閱: 針對(duì)系統(tǒng)程序員的生成器妙招。
對(duì)每個(gè)測(cè)試方法應(yīng)用相同的補(bǔ)丁
If you want several patches in place for multiple test methods the obvious way is to apply the patch decorators to every method. This can feel like unnecessary repetition. Instead, you can use patch() (in all its various forms) as a class decorator. This applies the patches to all test methods on the class. A test method is identified by methods whose names start with 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'
另一種管理補(bǔ)丁的方式是使用 補(bǔ)丁方法: start 和 stop。 它允許你將打補(bǔ)丁操作移至你的 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()
如果你要使用這個(gè)技巧則你必須通過(guò)調(diào)用 stop 來(lái)確保補(bǔ)丁被“恢復(fù)”。 這可能要比你想像的更麻煩,因?yàn)槿绻?setUp 中引發(fā)了異常那么 tearDown 將不會(huì)被調(diào)用。 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()
模擬未綁定方法
當(dāng)前在編寫(xiě)測(cè)試時(shí)我需要修補(bǔ)一個(gè) 未綁定方法 (在類(lèi)上而不是在實(shí)例上為方法打補(bǔ)丁)。 我需要將 self 作為第一個(gè)參數(shù)傳入因?yàn)槲蚁雽?duì)哪些對(duì)象在調(diào)用這個(gè)特定方法進(jìn)行斷言。 問(wèn)題是這里你不能用 mock 來(lái)打補(bǔ)丁,因?yàn)槿绻阌?mock 來(lái)替換一個(gè)未綁定方法那么當(dāng)從實(shí)例中獲取時(shí)它就不會(huì)成為一個(gè)已綁定方法,因而它不會(huì)獲得傳入的 self。 繞過(guò)此問(wèn)題的辦法是改用一個(gè)真正的函數(shù)來(lái)修補(bǔ)未綁定方法。 patch() 裝飾器讓使用 mock 來(lái)給方法打補(bǔ)丁變得如此簡(jiǎn)單以至于創(chuàng)建一個(gè)真正的函數(shù)成為一件麻煩事。
如果將 autospec=True 傳給 patch 那么它就會(huì)用一個(gè) 真正的 函數(shù)對(duì)象來(lái)打補(bǔ)丁。 這個(gè)函數(shù)對(duì)象具有與它所替換的函數(shù)相同的簽名,但會(huì)在內(nèi)部將操作委托給一個(gè) mock。 你仍然可以通過(guò)與以前完全相同的方式來(lái)自動(dòng)創(chuàng)建你的 mock。 但是這將意味著一件事,就是如果你用它來(lái)修補(bǔ)一個(gè)類(lèi)上的非綁定方法那么如果它是從一個(gè)實(shí)例中獲取則被模擬的函數(shù)將被轉(zhuǎn)為已綁定方法。 傳給它的第一個(gè)參數(shù)將為 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 那么這個(gè)未綁定方法會(huì)改為通過(guò)一個(gè) Mock 補(bǔ)丁來(lái)修補(bǔ),而不是附帶 self 來(lái)調(diào)用。
通過(guò) mock 檢查多次調(diào)用
mock 有一個(gè)很好的 API 用于針對(duì)你的 mock 對(duì)象如何被使用來(lái)下斷言。
>>> mock = Mock()>>> mock.foo_bar.
新聞標(biāo)題:創(chuàng)新互聯(lián)Python教程:unittest.mock —- 上手指南
標(biāo)題來(lái)源:http://www.dlmjj.cn/article/djghoeg.html


咨詢(xún)
建站咨詢(xún)
