數據類型、模型或節點——這些都只是mock對象可承擔的角色。但mock在單元測試中扮演一個什麼角色呢?html
有時,你須要爲單元測試的初始設置準備一些「其餘」的代碼資源。但這些資源興許會不可用,不穩定,或者是使用起來太笨重。你能夠試着找一些其餘的資源替代;或者你能夠經過建立一個被稱爲mock的東西來模擬它。Mocks可以讓咱們模擬那些在單元測試中不可用或太笨重的資源。
在Python中建立mock是經過Mock模塊完成的。你能夠經過每次一個屬性(one-attribute-at-a-time)或一個健全的字典對象或是一個類接口來建立mock。你還能夠定義mock的行爲而且在測試過程當中檢查它的使用。讓咱們繼續探討。python
典型的測試準備最少有兩個部分。首先是測試對象(紅色),這是測試的關注點。它能夠是一個方法、模塊或者類。它能夠返回一個結果,也能夠不返回結果,可是它能夠根據數據數據或者內部狀態產生錯誤或者異常(圖1)。web
圖1算法
第二測試用例(灰色),它能夠單獨運行也能夠做爲套件的一部分。它是爲測試對象準備的,也能夠是測試對象須要的任意數據或資源。運行一個或多個測試事務,在每一個測試中檢查測試對象的行爲。收集測試結果並用一個簡潔、易讀的格式呈現測試結果。app
如今,爲了發揮做用,一些測試對象須要一個或多個資源(綠色)。這些資源能夠是其餘的類或者模塊,甚至是一個非獨立的進程。不論其性質,測試資源是功能性的代碼。他們的角色是支持測試對象,可是他們不是測試的關注點。ide
可是有些時候,測試資源不可用,或者不適合。也許這個資源正在和測試對象並行開發中,或者並不完整或者是太不穩定以致於不可靠。單元測試
測試資源太昂貴,若是測試資源是第三方的產品,其高昂的價格不適用於測試。測試資源的創建過於複雜,佔用的硬件和時間能夠用於別的地方。若是測試資源是一個數據源,創建它的數據集模仿真實世界是乏味的。測試
測試資源是不可預知的。一個好的單元測試是可重複的,容許你分離和識別故障。可是測試資源可能給出隨機的結果,或者它會有不一樣的響應時間。而做爲這樣的結果,測試資源最終可能成爲一個潛在的攪局者。spa
這些都是你可能想要用mock代替測試資源的緣由。mock向測試對象提供一套和測試資源相同的方法接口。可是mock是更容易建立和管理。它能向測試對象提供和真實的測試資源相同的方法接口。它能提供肯定的結果,並能夠自定義以適用於特定的測試。可以容易的更新,以反映實際資源的變化。設計
固然,mocks不是沒有問題的。設計一個精確的mock是困難的,特別是若是你沒有測試資源的可靠信息。你能夠嘗試找到一個開源的接口,或者你能對測試資源的方法接口進行猜想。不管你如何選擇,你均可以在之後輕鬆的更新mock,你能夠在首選資源中獲得更詳細的信息。
太多的mock會使測試過於複雜,讓你跟蹤錯誤變得更困難。最好的實踐是每一個測試用例限制使用一到兩個mock,或者爲每一個mock/對象對使用獨立的測試用例。
Mock不是模仿測試資源的惟一方式。其餘的解決方案如stub和fake也能提供相同的服務。所以,mock和其餘兩種解決方案怎樣比較?爲何選擇mock而不是選擇stub或者fake?
認識stub:stub爲測試對象提供了一套方法接口,和真實的測試資源提供給測試對象的接口是相同的。當測試對象調用stub方法時,stub響應預約的結果。也能夠產生一個預約的錯誤或者異常。stub能夠跟蹤和測試對象的交互,可是它不處理輸入的數據。
fake也提供了一套方法接口而且也能夠跟蹤和測試對象的交互。可是和stub不一樣,fake真正的處理了從測試對象輸入的數據產生的結果是基於這些數據的。簡而言之,fake是功能性的,它是真實測試資源的非生產版。它缺少資源的相互制衡,使用了更簡單的算法,並且它不多存儲和傳輸數據。
使用fake和stub,你能夠輸入正確的數據調用了正確的方法對測試對象進行測試。你能測試對象是如何處理數據併產生結果,當出現錯誤或者異常時是怎樣反應的。這些測試被稱爲狀態驗證。可是你是否想知道測試對象調用了兩次相同的方法?你是否想知道測試對象是否按照正確的順序調用了幾個方法?這種測試被稱爲行爲驗證,而要作到這些,你須要mocks。
在Python中Mock模塊是用來建立和管理mock對象的。該模塊是Michael Foord的心血結晶,它是Python3.0的標準模塊。所以在Python2.4~2.7中,你不得不本身安裝這個模塊。你能夠 Python Package Index website從得到Mock模塊最新的版本。
基於你的mock對象,Mock模塊提供了少許的類。爲了改變運行中的mock甚至提供了補丁機制。可是如今,咱們關注一個類:Mock類。
圖2中顯示了Mock類(綠色)的基本結構。它繼承於兩個父類:NonCallableMock和CallableMixin(灰色)。NonCallableMock定義了mock對象所需的例程。它重載了幾個魔法方法,給他們定義了缺省的行爲。爲了跟蹤mock的行爲,它提供了斷言例程。CallableMixin更新了mock對象回調的魔法方法。反過來,兩個父類繼承於Base類(紅色),聲明瞭mock對象所需的屬性。
圖2
Mock類有四套方法(圖3)。第一套方法是類的構造器,它有六個可選和已標記的參數。圖中顯示了4個常常用到的參數。
圖3
構造器的第一個參數是name,它定義了mock對象的惟一標示符。Listing one顯示了怎麼建立一個標示符爲Foo的mock對象mockFoo。請注意當我打印mock對象(6-9行)時,標示符後緊跟的是mock對象的惟一ID。
構造器的第一個參數是name,它定義了mock對象的惟一標示符。Listing one顯示了怎麼建立一個標示符爲Foo的mock對象mockFoo。請注意當我打印mock對象(6-9行)時,標示符後緊跟的是mock對象的惟一ID。
Listing One
1 from mock import Mock 2 3 #create the mock object 4 mockFoo = Mock(name = "Foo") 5 6 print mockFoo 7 8 print repr(mockFoo)
構造器的第二個參數是spec。它設置mock對象的屬性,能夠是property或者方法。屬性能夠是一個列表字符串或者是其餘的Python類。
爲了演示,在Listing Two中,我有一個帶三個項目的列表對象fooSpec(第4行):property屬性_fooValue,方法屬性callFoo和doFoo。當我把fooSpec帶入類構造器時(第7行),mockFoo得到了三個屬性,我能用點操做符訪問它們(10~15行)。當我訪問了一個未聲明的屬性時,mockFoo引起AttributeError和"faulty"屬性(21~14行)。
Listing Two
1 from mock import Mock 2 3 # prepare the spec list 4 fooSpec = ["_fooValue", "callFoo", "doFoo"] 5 6 # create the mock object 7 mockFoo = Mock(spec = fooSpec) 8 9 # accessing the mocked attributes 10 print mockFoo 11 # <Mock id='427280'> 12 print mockFoo._fooValue 13 # returns <Mock name='mock._fooValue' id='2788112'> 14 print mockFoo.callFoo() 15 # returns: <Mock name='mock.callFoo()' id='2815376'> 16 17 mockFoo.callFoo() 18 # nothing happens, which is fine 19 20 # accessing the missing attributes 21 print mockFoo._fooBar 22 # raises: AttributeError: Mock object has no attribute '_fooBar' 23 mockFoo.callFoobar() 24 # raises: AttributeError: Mock object has no attribute 'callFoobar'
Listing Three顯示了spec參數的另外一種用法。此次,有帶三個屬性的類Foo(4~12行)。把類名傳入構造器中,這樣就產生了一個和Foo有相同屬性的mock對象(18~23行)。再次,訪問一個未聲明的屬性引起了AttributeError(29~32行)。也就是說,在兩個例子中,方法屬性時沒有功能的。甚至在方法有功能代碼時,調用mock的方法也什麼都不作。
Listing Three
1 from mock import Mock 2 3 # The class interfaces 4 class Foo(object): 5 # instance properties 6 _fooValue = 123 7 8 def callFoo(self): 9 print "Foo:callFoo_" 10 11 def doFoo(self, argValue): 12 print "Foo:doFoo:input = ", argValue 13 14 # create the mock object 15 mockFoo = Mock(spec = Foo) 16 17 # accessing the mocked attributes 18 print mockFoo 19 # returns <Mock spec='Foo' id='507120'> 20 print mockFoo._fooValue 21 # returns <Mock name='mock._fooValue' id='2788112'> 22 print mockFoo.callFoo() 23 # returns: <Mock name='mock.callFoo()' id='2815376'> 24 25 mockFoo.callFoo() 26 # nothing happens, which is fine 27 28 # accessing the missing attributes 29 print mockFoo._fooBar 30 # raises: AttributeError: Mock object has no attribute '_fooBar' 31 mockFoo.callFoobar() 32 # raises: AttributeError: Mock object has no attribute 'callFoobar'
下一個構造器參數是return_value。這將設置mock對象的響應當它被直接調用的時候。我用這個參數模擬一個工廠調用。
爲了演示,在Listing Four中,設置return_value爲456(第4行)。當調用mockFoo時,將返回456的結果(9~11行)。在Listing Five中,我給return_value傳入了一個類Foo的實例fooObj(15~19行)。如今,當我調用mockFoo時,我得到了fooObj的實例(顯示爲mockObj)(第24行)。和Listing Two和Three不同,mockObj的方法是帶有功能的。
Listing Four
1 from mock import Mock 2 3 # create the mock object 4 mockFoo = Mock(return_value = 456) 5 6 print mockFoo 7 # <Mock id='2787568'> 8 9 mockObj = mockFoo() 10 print mockObj 11 # returns: 456
Listing Five
1 from mock import Mock 2 3 # The mock object 4 class Foo(object): 5 # instance properties 6 _fooValue = 123 7 8 def callFoo(self): 9 print "Foo:callFoo_" 10 11 def doFoo(self, argValue): 12 print "Foo:doFoo:input = ", argValue 13 14 # creating the mock object 15 fooObj = Foo() 16 print fooObj 17 # returns: <__main__.Foo object at 0x68550> 18 19 mockFoo = Mock(return_value = fooObj) 20 print mockFoo 21 # returns: <Mock id='2788144'> 22 23 # creating an "instance" 24 mockObj = mockFoo() 25 print mockObj 26 # returns: <__main__.Foo object at 0x68550> 27 28 # working with the mocked instance 29 print mockObj._fooValue 30 # returns: 123 31 mockObj.callFoo() 32 # returns: Foo:callFoo_ 33 mockObj.doFoo("narf") 34 # returns: Foo:doFoo:input = narf 35 <Mock id='428560'>
side_effect參數和return_value是相反的。它給mock分配了可替換的結果,覆蓋了return_value。簡單的說,一個模擬工廠調用將返回side_effect值,而不是return_value。
Listing Six演示了side_effect參數的影響。首先,建立類Foo的實例fooObj,把它傳入return_value參數(第17行)。這個結果和Listing Five是相似的。當它被調用的時候,mockFoo返回fooObj(第22行)。而後我重複一樣的步驟,給side_effect參數傳入StandardError(第28行),如今,調用mockFoo引起了StandardError,再也不返回fooObj(29~30行)。
Listing Six
1 from mock import Mock 2 3 # The mock object 4 class Foo(object): 5 # instance properties 6 _fooValue = 123 7 8 def callFoo(self): 9 print "Foo:callFoo_" 10 11 def doFoo(self, argValue): 12 print "Foo:doFoo:input = ", argValue 13 14 # creating the mock object (without a side effect) 15 fooObj = Foo() 16 17 mockFoo = Mock(return_value = fooObj) 18 print mockFoo 19 # returns: <Mock id='2788144'> 20 21 # creating an "instance" 22 mockObj = mockFoo() 23 print mockObj 24 # returns: <__main__.Foo object at 0x2a88f0> 25 26 # creating a mock object (with a side effect) 27 28 mockFoo = Mock(return_value = fooObj, side_effect = StandardError) 29 mockObj = mockFoo() 30 # raises: StandardError
Listing Seven顯示了另外一個影響。在這個例子中,傳入一個列表對象fooList到類構造器中(17~18行)。而後,每次我調用mockFoo時,它連續的返回列表中的項(20~30行)。一旦mockFoo到達了列表的末尾,調用將引起StopIteration 錯誤(32~34行)
Listing Seven
1 from mock import Mock 2 3 # The mock object 4 class Foo(object): 5 # instance properties 6 _fooValue = 123 7 8 def callFoo(self): 9 print "Foo:callFoo_" 10 11 def doFoo(self, argValue): 12 print "Foo:doFoo:input = ", argValue 13 14 # creating the mock object (with a side effect) 15 fooObj = FooSpec() 16 17 fooList = [665, 666, 667] 18 mockFoo = Mock(return_value = fooObj, side_effect = fooList) 19 20 fooTest = mockFoo() 21 print fooTest 22 # returns 665 23 24 fooTest = mockFoo() 25 print fooTest 26 # returns 666 27 28 fooTest = mockFoo() 29 print fooTest 30 # returns 667 31 32 fooTest = mockFoo() 33 print fooTest 34 # raises: StopIteration
你能夠傳入其餘的可迭代對象(集合,元組)到side_effct對象中。你不能傳入一個簡單對象(如整數、字符串等),由於這些對象是不能迭代的,爲了讓簡單對象可迭代,須要將他們加入單一項的列表中。
Mock類的下一套方法是斷言。它將幫助跟蹤測試對象對mock方法的調用。他們能和unittest模塊的斷言一塊兒工做。能鏈接到mock或者其方法屬性之一。 有兩個相同的可選參數:一個變量序列,一個鍵/值序列。
第一個斷言assert_called_with(),檢查mock方法是否得到了正確的參數。當至少一個參數有錯誤的值或者類型時,當參數的數量錯誤時,當參數的順序錯誤時,或者當mock的方法根本不存在任何參數時,這個斷言將引起錯誤。Listing Eight顯示了能夠怎樣使用這個斷言。那兒,我準備了一個mock對象,用類Foo做爲它的spec參數。我調用了類的方法doFoo(),傳入了一個字符串做爲輸入。使用assert_called_with(),我檢查方法是否得到了正確的輸入。第20行的斷言經過了,由於doFoo()得到了"narf"的輸入。可是在第24行的斷言失敗了由於doFoo()得到了"zort",這是錯誤的輸入。
Listing Eight
1 from mock import Mock 2 3 # The mock object 4 class Foo(object): 5 # instance properties 6 _fooValue = 123 7 8 def callFoo(self): 9 pass 10 11 def doFoo(self, argValue): 12 pass 13 14 # create the mock object 15 mockFoo = Mock(spec = Foo) 16 print mockFoo 17 # returns <Mock spec='Foo' id='507120'> 18 19 mockFoo.doFoo("narf") 20 mockFoo.doFoo.assert_called_with("narf") 21 # assertion passes 22 23 mockFoo.doFoo("zort") 24 mockFoo.doFoo.assert_called_with("narf") 25 # AssertionError: Expected call: doFoo('narf') 26 # Actual call: doFoo('zort')
Listing Nine顯示了稍微不一樣的用法。在這個例子中,我調用了mock方法callFoo(),首先沒有任何輸入,而後輸入了字符串「zort」。第一個斷言經過了(第20行),由於callFoo()不支持得到任何輸入。而第二個斷言失敗了(第24行)由於顯而易見的緣由。
Listing Nine
1 from mock import Mock 2 3 # The mock object 4 class Foo(object): 5 # instance properties 6 _fooValue = 123 7 8 def callFoo(self): 9 pass 10 11 def doFoo(self, argValue): 12 pass 13 14 # create the mock object 15 mockFoo = Mock(spec = Foo) 16 print mockFoo 17 # returns <Mock spec='Foo' id='507120'> 18 19 mockFoo.callFoo() 20 mockFoo.callFoo.assert_called_with() 21 # assertion passes 22 23 mockFoo.callFoo("zort") 24 mockFoo.callFoo.assert_called_with() 25 # AssertionError: Expected call: callFoo() 26 # Actual call: callFoo('zort')
先一個斷言是assert_called_once_with()。像assert_called_with()同樣,這個斷言檢查測試對象是否正確的調用了mock方法。可是當一樣的方法調用超過一次時, assert_called_once_with()將引起錯誤,然而assert_called_with()會忽略屢次調用。Listing Ten顯示了怎樣使用這個斷言。那兒,我調用了mock方法callFoo()兩次。第一次調用時(行19~20),斷言經過。可是在第二次調用的時(行23~24),斷言失敗,發送了錯誤消息到stdout。
Listing Ten
1 from mock import Mock 2 3 # The mock object 4 class Foo(object): 5 # instance properties 6 _fooValue = 123 7 8 def callFoo(self): 9 pass 10 11 def doFoo(self, argValue): 12 pass 13 14 # create the mock object 15 mockFoo = Mock(spec = Foo) 16 print mockFoo 17 # returns <Mock spec='Foo' id='507120'> 18 19 mockFoo.callFoo() 20 mockFoo.callFoo.assert_called_once_with() 21 # assertion passes 22 23 mockFoo.callFoo() 24 mockFoo.callFoo.assert_called_once_with() 25 # AssertionError: Expected to be called once. Called 2 times.
斷言assert_any_call(),檢查測試對象在測試例程中是否調用了測試方法。它無論mock方法和斷言之間有多少其餘的調用。和前面兩個斷言相比較,前兩個斷言僅檢查最近一次的調用。
Listing Eleven顯示了assert_any_call()斷言如何工做:仍然是一樣的mock對象,spec參數是Foo類。第一個調用方法callFoo()(第18行),接下來調用兩次doFoo()(行19~20)。注意doFoo()得到了兩個不一樣的輸入。
Listing Eleven
1 <from mock import Mock 2 3 # The mock specification 4 class Foo(object): 5 _fooValue = 123 6 7 def callFoo(self): 8 pass 9 10 def doFoo(self, argValue): 11 pass 12 13 # create the mock object 14 mockFoo = Mock(spec = Foo) 15 print mockFoo 16 # returns <Mock spec='Foo' id='507120'> 17 18 mockFoo.callFoo() 19 mockFoo.doFoo("narf") 20 mockFoo.doFoo("zort") 21 22 mockFoo.callFoo.assert_any_call() 23 # assert passes 24 25 mockFoo.callFoo() 26 mockFoo.doFoo("troz") 27 28 mockFoo.doFoo.assert_any_call("zort") 29 # assert passes 30 31 mockFoo.doFoo.assert_any_call("egad") 32 # raises: AssertionError: doFoo('egad') call not found
第一個assert_any_call()(第22行)經過,雖然兩次doFoo()調用隔開了斷言和callFoo()。第二個斷言(第28行)也經過了,雖然一個callFoo()隔開了咱們提到的doFoo()(第20行)。另外一方面,第三個斷言(第31行)失敗了,由於沒有任何doFoo()的調用使用了"egad"的輸入。
最後,還有assert_has_calls()。它查看方法調用的順序,檢查他們是否按正確的次序調用並帶有正確的參數。它帶有兩個參數:指望調用方法的列表和一個可選懸殊any_order。當測試對象調用了錯誤的方法,調用了不在次序中的方法,或者方法得到了一個錯誤的輸入,將生產斷言錯誤。
Listing Twelve演示了assert_has_calls()斷言。在18~20行,我調用了三個方法,提供了兩個輸入。而後,我準備了一個指望調用的列表(fooCalls)並把這個列表傳入assert_has_calls()(22~23行)。因爲列表匹配了方法的調用,斷言經過。
Listing Twelve
1 from mock import Mock, call 2 3 # The mock specification 4 class Foo(object): 5 _fooValue = 123 6 7 def callFoo(self): 8 pass 9 10 def doFoo(self, argValue): 11 pass 12 13 # create the mock object 14 mockFoo = Mock(spec = Foo) 15 print mockFoo 16 # returns <Mock spec='Foo' id='507120'> 17 18 mockFoo.callFoo() 19 mockFoo.doFoo("narf") 20 mockFoo.doFoo("zort") 21 22 fooCalls = [call.callFoo(), call.doFoo("narf"), call.doFoo("zort")] 23 mockFoo.assert_has_calls(fooCalls) 24 # assert passes 25 26 fooCalls = [call.callFoo(), call.doFoo("zort"), call.doFoo("narf")] 27 mockFoo.assert_has_calls(fooCalls) 28 # AssertionError: Calls not found. 29 # Expected: [call.callFoo(), call.doFoo('zort'), call.doFoo('narf')] 30 # Actual: [call.callFoo(), call.doFoo('narf'), call.doFoo('zort')] 31 32 fooCalls = [call.callFoo(), call.doFoo("zort"), call.doFoo("narf")] 33 mockFoo.assert_has_calls(fooCalls, any_order = True)
在第26行,我交換了兩個doFoo()調用的順序。第一個doFoo()得到"zort"的輸入,第二個得到了"narf"。若是我傳入這個fooCalls到assert_has_calls()(第27行)中,斷言失敗。可是若是我給參數any_order傳入參數True,斷言經過。這是由於斷言將忽略方法調用的順序。
Listing Thirteen演示了其餘的用法。在fooCalls列表中,我添加了不存在的方法dooFoo()(第22行)。而後我傳入fooCalls到assert_has_calls()中(第24行)。斷言失敗,通知我指望調用的順序和真實發生的順序不匹配。若是我給any_order賦值爲True(第30行),斷言名稱dooFoo()做爲違規的方法調用。
Listing Thirteen
1 from mock import Mock, call 2 3 # The mock specification 4 class Foo(object): 5 _fooValue = 123 6 7 def callFoo(self): 8 pass 9 10 def doFoo(self, argValue): 11 pass 12 13 # create the mock object 14 mockFoo = Mock(spec = Foo) 15 print mockFoo 16 # returns <Mock spec='Foo' id='507120'> 17 18 mockFoo.callFoo() 19 mockFoo.doFoo("narf") 20 mockFoo.doFoo("zort") 21 22 fooCalls = [call.callFoo(), call.dooFoo("narf"), call.doFoo("zort")] 23 24 mockFoo.assert_has_calls(fooCalls) 25 # AssertionError: Calls not found. 26 # Expected: [call.callFoo(), call.dooFoo('narf'), call.doFoo('zort')] 27 # Actual: [call.callFoo(), call.doFoo('narf'), call.doFoo('zort')] 28 29 fooCalls = [call.callFoo(), call.dooFoo("narf"), call.doFoo("zort")] 30 mockFoo.assert_has_calls(fooCalls, any_order = True) 31 # AssertionError: (call.dooFoo('narf'),) not all found in call list
在assert_has_calls()的兩個例子中,注意到關鍵字call是出如今每一個方法的前面。這個關鍵字是一個helper對象,標記出mock對象的方法屬性。爲了使用call關鍵字,請確保使用以下的方法從mocke模塊導入helper:
1 from mock import Mock, call
Mock類的第三套方法容許你控制和管理mock對象。你能夠更改mock的行爲,改變它的屬性或者將mock恢復到測試前的狀態。你甚至能夠更改每一個mock方法或者mock自己的響應值。attach_mock()方法讓你在mock中添加第二個mock對象。這個方法帶有兩個參數:第二個mock對象(aMock)和一個屬性名稱(aName)。
Listing Fourteen 樣式了attach_mock()方法的使用。那兒,我建立了兩個mock對象mockFoo和mockBar,他們有不一樣spec參數(第25行和第30行)。我用attach_mock()方法將mockBar添加到mockFoo中,命名爲fooBar(第35行)。一旦添加成功,我就能經過property fooBar訪問第二mock對象和它的屬性(46~53行)。而且我仍然能夠訪問第一個mock對象mockFoo的屬性。
Listing Fourteen
1 from mock import Mock 2 3 # The mock object 4 class Foo(object): 5 # instance properties 6 _fooValue = 123 7 8 def callFoo(self): 9 print "Foo:callFoo_" 10 11 def doFoo(self, argValue): 12 print "Foo:doFoo:input = ", argValue 13 14 class Bar(object): 15 # instance properties 16 _barValue = 456 17 18 def callBar(self): 19 pass 20 21 def doBar(self, argValue): 22 pass 23 24 # create the first mock object 25 mockFoo = Mock(spec = Foo) 26 print mockFoo 27 # returns <Mock spec='Foo' id='507120'> 28 29 # create the second mock object 30 mockBar = Mock(spec = Bar) 31 print mockBar 32 # returns: <Mock spec='Bar' id='2784400'> 33 34 # attach the second mock to the first 35 mockFoo.attach_mock(mockBar, 'fooBar') 36 37 # access the first mock's attributes 38 print mockFoo 39 # returns: <Mock spec='Foo' id='495312'> 40 print mockFoo._fooValue 41 # returns: <Mock name='mock._fooValue' id='428976'> 42 print mockFoo.callFoo() 43 # returns: <Mock name='mock.callFoo()' id='448144'> 44 45 # access the second mock and its attributes 46 print mockFoo.fooBar 47 # returns: <Mock name='mock.fooBar' spec='Bar' id='2788592'> 48 print mockFoo.fooBar._barValue 49 # returns: <Mock name='mock.fooBar._barValue' id='2788016'> 50 print mockFoo.fooBar.callBar() 51 # returns: <Mock name='mock.fooBar.callBar()' id='2819344'> 52 print mockFoo.fooBar.doBar("narf") 53 # returns: <Mock name='mock.fooBar.doBar()' id='4544528'>
configure_mock()方法讓你批量的更改mock對象。它惟一的參數是一個鍵值對序列,每一個鍵就是你想要修改的屬性。若是你的對象沒有指定的屬性,configure_mock()將在mock中添加屬性。
Listing fifteen顯示了configure_mock()方法的運用。再次,我定義了一個spec爲類Foo和return_value爲555的mock對象mockFoo(第13行)。而後使用configure_mock()方法更改return_value爲999(第17行)。當我直接調用mockFoo時,得到的結果爲999,替換了原來的555。
Listing Fifteen
1 from mock import Mock 2 3 class Foo(object): 4 # instance properties 5 _fooValue = 123 6 7 def callFoo(self): 8 print "Foo:callFoo_" 9 10 def doFoo(self, argValue): 11 print "Foo:doFoo:input = ", argValue 12 13 mockFoo = Mock(spec = Foo, return_value = 555) 14 print mockFoo() 15 # returns: 555 16 17 mockFoo.configure_mock(return_value = 999) 18 print mockFoo() 19 # returns: 999 20 21 fooSpec = {'callFoo.return_value':"narf", 'doFoo.return_value':"zort", 'doFoo.side_effect':StandardError} 22 mockFoo.configure_mock(**fooSpec) 23 24 print mockFoo.callFoo() 25 # returns: narf 26 print mockFoo.doFoo("narf") 27 # raises: StandardError 28 29 fooSpec = {'doFoo.side_effect':None} 30 mockFoo.configure_mock(**fooSpec) 31 print mockFoo.doFoo("narf") 32 # returns: zort
接着,我準備了一個字段對象(fooSpec),對兩個mock方法設置了返回值,爲doFoo()設置了side_effect(第21行)。我將fooSpec傳入configure_mock(),注意fooSpec帶有前綴'**'(第22行)。如今調用callFoo()結果返回「narf」。調用doFoo(),不管輸入什麼,引起StandardError 信號(行24~27)。若是我修改了fooSpec,設置doFoo()的side_effect的值爲None,當我調用doFoo()時,將獲得結果「zort」(29~32行)。
下一個方法mock_add_spec()讓你向mock對象添加新的屬性。除了mock_add_spec()工做在一個已存在的對象上以外,它的功能相似於構造器的spec參數。它擦除了一些構造器設置的屬性。這個方法帶有兩個參數:spec屬性(aSpec)和spc_set標誌(aFlag)。再次,spce能夠是字符串列表或者是類。已添加的屬性缺省狀態是隻讀的,可是經過設置spec_set標誌爲True,可讓屬性可寫。
Listing Sixteen演示了mock_add_spec()的運用。mock對象mockFoo開始的屬性來自於類Foo(第25行)。當我訪問兩個屬性(_fooValue和callFoo())時,我獲得結果確認他們是存在的(29~32行)。
Listing Sixteen
1 from mock import Mock 2 3 # The class interfaces 4 class Foo(object): 5 # instance properties 6 _fooValue = 123 7 8 def callFoo(self): 9 print "Foo:callFoo_" 10 11 def doFoo(self, argValue): 12 print "Foo:doFoo:input = ", argValue 13 14 class Bar(object): 15 # instance properties 16 _barValue = 456 17 18 def callBar(self): 19 pass 20 21 def doBar(self, argValue): 22 pass 23 24 # create the mock object 25 mockFoo = Mock(spec = Foo) 26 27 print mockFoo 28 # returns <Mock spec='Foo' id='507120'> 29 print mockFoo._fooValue 30 # returns <Mock name='mock._fooValue' id='2788112'> 31 print mockFoo.callFoo() 32 # returns: <Mock name='mock.callFoo()' id='2815376'> 33 34 # add a new spec attributes 35 mockFoo.mock_add_spec(Bar) 36 37 print mockFoo 38 # returns: <Mock spec='Bar' id='491088'> 39 print mockFoo._barValue 40 # returns: <Mock name='mock._barValue' id='2815120'> 41 print mockFoo.callBar() 42 # returns: <Mock name='mock.callBar()' id='4544368'> 43 44 print mockFoo._fooValue 45 # raises: AttributeError: Mock object has no attribute '_fooValue' 46 print mockFoo.callFoo() 47 # raises: AttributeError: Mock object has no attribute 'callFoo'
而後,我使用mock_add_spec()方法添加類Bar到mockFoo(第35行)。mock對象如今的屬性已聲明在類Bar中(39~42行)。若是我訪問任何Foo屬性,mock對象將引起AttributeError 信號,表示他們不存在(44~47行)。
最後一個方法resetMock(),恢復mock對象到測試前的狀態。它清除了mock對象的調用統計和斷言。它不會清除mock對象的return_value和side_effect屬性和它的方法屬性。這樣作是爲了從新使用mock對象避免從新建立mock的開銷。
最後,你能給每一個方法屬性分配返回值或者side-effect。你能經過return_value和side_effect訪問器作到這些。例如,按以下的語句經過return_value訪問器設置方法callFoo()的返回值爲"narf":
1 mockFoo.callFoo.return_value = "narf"
按以下的語句經過side_effect訪問器 設置方法callFoo()的side-ffect爲TypeError
1 mockFoo.callFoo.side_effect = TypeError
傳入None清除side-effect
1 mockFoo.callFoo.side_effect = None
你也能夠用這個兩個相同的訪問器改變mock對象對工廠調用的響應值。
最後一套方法包含跟蹤mock對象所作的任意調用的訪問器。當mock對象得到工廠調用時,訪問器called返回True,不然返回False。查看Listing Seventeen中的代碼,我建立了mockFoo以後,called訪問器返回告終果False(19~20行)。若是我作了一個工廠調用,它將返回結果True(22~23行)。可是若是我建立了第二個mock對象,而後調用了mock方法callFoo()(第30行)?在這個例子中,called訪問器僅僅放回了False結果(31~32行)。
Listing Seventeen
1 from mock import Mock 2 3 # The mock object 4 class Foo(object): 5 # instance properties 6 _fooValue = 123 7 8 def callFoo(self): 9 print "Foo:callFoo_" 10 11 def doFoo(self, argValue): 12 print "Foo:doFoo:input = ", argValue 13 14 # create the first mock object 15 mockFoo = Mock(spec = Foo) 16 print mockFoo 17 # returns <Mock spec='Foo' id='507120'> 18 19 print mockFoo.called 20 # returns: False 21 22 mockFoo() 23 print mockFoo.called 24 # returns: True 25 26 mockFoo = Mock(spec = Foo) 27 print mockFoo.called 28 # returns: False 29 30 mockFoo.callFoo() 31 print mockFoo.called 32 # returns: False
訪問器call_count給出了mock對象被工廠調用的次數。查看Listing Eighteen中的代碼。我建立mockFoo以後,call_count給出的指望結果爲0(19~20行)。當我對mockFoo作了一個工廠調用時,call_count增長1(22~24行)。當我調用mock方法callFoo()時,call_count沒有改變(26~28行)。若是我作了第二次工廠調用call_count將再增長1。
Listing Eighteen
1 from mock import Mock 2 3 # The mock object 4 class Foo(object): 5 # instance properties 6 _fooValue = 123 7 8 def callFoo(self): 9 print "Foo:callFoo_" 10 11 def doFoo(self, argValue): 12 print "Foo:doFoo:input = ", argValue 13 14 # create the first mock object 15 mockFoo = Mock(spec = Foo) 16 print mockFoo 17 # returns <Mock spec='Foo' id='507120'> 18 19 print mockFoo.call_count 20 # returns: 0 21 22 mockFoo() 23 print mockFoo.call_count 24 # returns: 1 25 26 mockFoo.callFoo() 27 print mockFoo.call_count 28 # returns: 1
訪問器call_args返回工廠調用已用的參數。Listing Nineteen演示了它的運用。對於新建立的mock對象(mockFoo),call_args訪問器返回結果爲None(17~21行)。若是我作了一個工廠調用,在輸入中傳入"zort",call_args報告的結果爲call('zort')(23~25行)。注意結果中的call關鍵字。對於第二個沒有輸入的工廠調用,call_args返回call()(27~29行)。第三個工廠調用,輸入「troz」,call_args給出結果爲call('troz')(31~33行)。可是當我調用mock方法callFoo()時,call_args訪問器仍然返回call('troz')(35~37行)。
Listing Nineteen
1 #!/usr/bin/python 2 3 from mock import Mock 4 5 # The mock object 6 class Foo(object): 7 # instance properties 8 _fooValue = 123 9 10 def callFoo(self): 11 print "Foo:callFoo_" 12 13 def doFoo(self, argValue): 14 print "Foo:doFoo:input = ", argValue 15 16 # create the first mock object 17 mockFoo = Mock(spec = Foo, return_value = "narf") 18 print mockFoo 19 # returns <Mock spec='Foo' id='507120'> 20 print mockFoo.call_args 21 # returns: None 22 23 mockFoo("zort") 24 print mockFoo.call_args 25 # returns: call('zort') 26 27 mockFoo() 28 print mockFoo.call_args 29 # returns: call() 30 31 mockFoo("troz") 32 print mockFoo.call_args 33 # returns: call('troz') 34 35 mockFoo.callFoo() 36 print mockFoo.call_args 37 # returns: call('troz')
訪問器call_args_list 也報告了工廠調用中已使用的參數。可是call_args返回最近使用的參數,而call_args_list返回一個列表,第一項爲最先的參數。Listing Twenty顯示了這個訪問的的運用,使用了和Listing Nineteen相同的代碼。
Listing Twenty
1 from mock import Mock 2 3 # The mock object 4 class Foo(object): 5 # instance properties 6 _fooValue = 123 7 8 def callFoo(self): 9 print "Foo:callFoo_" 10 11 def doFoo(self, argValue): 12 print "Foo:doFoo:input = ", argValue 13 14 # create the first mock object 15 mockFoo = Mock(spec = Foo, return_value = "narf") 16 print mockFoo 17 # returns <Mock spec='Foo' id='507120'> 18 19 mockFoo("zort") 20 print mockFoo.call_args_list 21 # returns: [call('zort')] 22 23 mockFoo() 24 print mockFoo.call_args_list 25 # returns: [call('zort'), call()] 26 27 mockFoo("troz") 28 print mockFoo.call_args_list 29 # returns: [call('zort'), call(), call('troz')] 30 31 mockFoo.callFoo() 32 print mockFoo.call_args_list 33 # returns: [call('zort'), call(), call('troz')]
訪問器mothod_calls報告了測試對象所作的mock方法的調用。它的結果是一個列表對象,每一項顯示了方法的名稱和它的參數。
Listing Twenty-one演示了method_calls的運用。對新建立的mockFoo,method_calls返回了空列表(15~19行)。當作了工廠調用時,一樣返回空列表(21~23行)。當我調用了mock方法callFoo()時,method_calls返回一個帶一項數據的列表對象(25~27行)。當我調用doFoo(),並傳入"narf"參數時,method_calls返回帶有兩項數據的列表(29~31行)。注意每一個方法名稱是按照它調用的順序顯示的。
Listing Twenty-one
1 from mock import Mock 2 3 # The mock object 4 class Foo(object): 5 # instance properties 6 _fooValue = 123 7 8 def callFoo(self): 9 print "Foo:callFoo_" 10 11 def doFoo(self, argValue): 12 print "Foo:doFoo:input = ", argValue 13 14 # create the first mock object 15 mockFoo = Mock(spec = Foo, return_value = "poink") 16 print mockFoo 17 # returns <Mock spec='Foo' id='507120'> 18 print mockFoo.method_calls 19 # returns [] 20 21 mockFoo() 22 print mockFoo.method_calls 23 # returns [] 24 25 mockFoo.callFoo() 26 print mockFoo.method_calls 27 # returns: [call.callFoo()] 28 29 mockFoo.doFoo("narf") 30 print mockFoo.method_calls 31 # returns: [call.callFoo(), call.doFoo('narf')] 32 33 mockFoo() 34 print mockFoo.method_calls 35 # returns: [call.callFoo(), call.doFoo('narf')]
最後一個訪問器mock_calls報告了測試對象對mock對象全部的調用。結果是一個列表,可是工廠調用和方法調用都顯示了。Listing Twenty-two演示這個訪問器的運用,使用了和Listing Twenty-one相同的代碼
Listing Twenty-two
1 from mock import Mock 2 3 # The mock object 4 class Foo(object): 5 # instance properties 6 _fooValue = 123 7 8 def callFoo(self): 9 print "Foo:callFoo_" 10 11 def doFoo(self, argValue): 12 print "Foo:doFoo:input = ", argValue 13 14 # create the first mock object 15 mockFoo = Mock(spec = Foo, return_value = "poink") 16 print mockFoo 17 # returns <Mock spec='Foo' id='507120'> 18 19 print mockFoo.mock_calls 20 # returns [] 21 22 mockFoo() 23 print mockFoo.mock_calls 24 # returns [call()] 25 26 mockFoo.callFoo()> 27 print mockFoo.mock_calls 28 # returns: [call(), call.callFoo()] 29 30 mockFoo.doFoo("narf") 31 print mockFoo.mock_calls 32 # returns: [call(), call.callFoo(), call.doFoo('narf')] 33 34 mockFoo() 35 print mockFoo.mock_calls 36 # returns: [call(), call.callFoo(), call.doFoo('narf'), call()]
數據類型,模型或者節點,這些是mock對象可能被假定的一些角色。可是mock對象怎樣適合單元測試呢?讓咱們一塊兒來看看,來自Martin Fowler的文章Mocks Aren't Stubs採起了簡化的設置。
在這個測試中,設置了三個類(圖4)。Order類是測試對象。它模擬了單一項目的採購訂單,訂單來源於一個數據源。Warehouse類是測試資源。它包含了鍵值對的序列,鍵是項目的名稱,值是可用的數量。OrderTest類是測試用例自己。
圖4
Listing Twenty-three描述了Order。Order類聲明瞭三個屬性:項目名稱(_orderItem),要求的數量(_orderAmount)和已填寫的數量(_orderFilled)。它的構造器帶有兩個參數(8~18行),填入的屬性是_orderItem和_orderAmount。它的__repr__()方法返回了購買清單的摘要(21~24行)。
Listing Twenty-three
1 class Order(object): 2 # instance properties 3 _orderItem = "None" 4 _orderAmount = 0 5 _orderFilled = -1 6 7 # Constructor 8 def __init__(self, argItem, argAmount): 9 print "Order:__init__" 10 11 # set the order item 12 if (isinstance(argItem, str)): 13 if (len(argItem) > 0): 14 self._orderItem = argItem 15 16 # set the order amount 17 if (argAmount > 0): 18 self._orderAmount = argAmount 19 20 # Magic methods 21 def __repr__(self): 22 # assemble the dictionary 23 locOrder = {'item':self._orderItem, 'amount':self._orderAmount} 24 return repr(locOrder) 25 26 # Instance methods 27 # attempt to fill the order 28 def fill(self, argSrc): 29 print "Order:fill_" 30 31 try: 32 # does the warehouse has the item in stock? 33 if (argSrc is not None): 34 if (argSrc.hasInventory(self._orderItem)): 35 # get the item 36 locCount = argSrc.getInventory(self._orderItem, self._orderAmount) 37 38 # update the following property 39 self._orderFilled = locCount 40 else: 41 print "Inventory item not available" 42 else: 43 print "Warehouse not available" 44 except TypeError: 45 print "Invalid warehouse" 46 47 # check if the order has been filled 48 def isFilled(self): 49 print "Order:isFilled_" 50 return (self._orderAmount == self._orderFilled)
Order類定義了兩個實例方法。fill()方法從參數(argSrc)中獲取數據源。它檢查數據源是否可用,數據源的項目是否存在問題(33~34行)。它提交了一個申請並用實際返回的數量更新_orderFilled(36~39行)。當_orderAmount和_orderFilled有相同的值時,isFilled()方法返回True(48~50行)。
Listing Twenty-four描述了Warehouse類。它是一個抽象類,聲明瞭屬性和方法接口,可是沒有定義方法自己。屬性_houseName是倉庫的名字,而_houseList是它持有的庫存。還有這兩個屬性的訪問器。
Listing Twenty-four
1 class Warehouse(object): 2 # private properties 3 _houseName = None 4 _houseList = None 5 6 # accessors 7 def warehouseName(self): 8 return (self._houseName) 9 10 def inventory(self): 11 return (self._houseList) 12 13 14 # -- INVENTORY ACTIONS 15 # set up the warehouse 16 def setup(self, argName, argList): 17 	pass 18 19 # check for an inventory item 20 def hasInventory(self, argItem): 21 pass 22 23 # retrieve an inventory item 24 def getInventory(self, argItem, argCount): 25 pass 26 27 # add an inventory item 28 def addInventory(self, argItem, argCount): 29 pass
Warehouse類聲明瞭四個方法接口。方法setup()帶有兩個參數,是爲了更新這兩個屬性。方法hasInventory()參數是項目的名稱,若是項目在庫存中則返回True。方法getInventory()的參數是項目的名稱和數量。它嘗試着從庫存中扣除數量,返回哪些是成功的扣除。方法addInventory()的參數也是項目名稱和數量。它將用這兩個參數更新_houseList。
Listing Twenty-five是測試用例自己,orderTest類。他有一個屬性fooSource是Order類所需的mock對象。setUp()方法識別執行的測試例程(14~16行),而後建立和配置mock對象(21~34行)。tearDown()方法向stdout打印一個空行。
Listing Twenty-five
1 import unittest 2 from mock import Mock, call 3 4 class OrderTest(unittest.TestCase): 5 # declare the test resource 6 fooSource = None 7 8 # preparing to test 9 def setUp(self): 10 """ Setting up for the test """ 11 print "OrderTest:setUp_:begin" 12 13 # identify the test routine 14 testName = self.id().split(".") 15 testName = testName[2] 16 print testName 17 18 # prepare and configure the test resource 19 if (testName == "testA_newOrder"): 20 print "OrderTest:setup_:testA_newOrder:RESERVED" 21 elif (testName == "testB_nilInventory"): 22 self.fooSource = Mock(spec = Warehouse, return_value = None) 23 elif (testName == "testC_orderCheck"): 24 self.fooSource = Mock(spec = Warehouse) 25 self.fooSource.hasInventory.return_value = True 26 self.fooSource.getInventory.return_value = 0 27 elif (testName == "testD_orderFilled"): 28 self.fooSource = Mock(spec = Warehouse) 29 self.fooSource.hasInventory.return_value = True 30 self.fooSource.getInventory.return_value = 10 31 elif (testName == "testE_orderIncomplete"): 32 self.fooSource = Mock(spec = Warehouse) 33 self.fooSource.hasInventory.return_value = True 34 self.fooSource.getInventory.return_value = 5 35 else: 36 print "UNSUPPORTED TEST ROUTINE" 37 38 # ending the test 39 def tearDown(self): 40 """Cleaning up after the test""" 41 print "OrderTest:tearDown_:begin" 42 print "" 43 44 # test: new order 45 # objective: creating an order 46 def testA_newOrder(self): 47 # creating a new order 48 testOrder = Order("mushrooms", 10) 49 print repr(testOrder) 50 51 # test for a nil object 52 self.assertIsNotNone(testOrder, "Order object is a nil.") 53 54 # test for a valid item name 55 testName = testOrder._orderItem 56 self.assertEqual(testName, "mushrooms", "Invalid item name") 57 58 # test for a valid item amount 59 testAmount = testOrder._orderAmount 60 self.assertGreater(testAmount, 0, "Invalid item amount") 61 62 # test: nil inventory 63 # objective: how the order object handles a nil inventory 64 def testB_nilInventory(self): 65 """Test routine B""" 66 # creating a new order 67 testOrder = Order("mushrooms", 10) 68 print repr(testOrder) 69 70 # fill the order 71 testSource = self.fooSource() 72 testOrder.fill(testSource) 73 74 # print the mocked calls 75 print self.fooSource.mock_calls 76 77 # check the call history 78 testCalls = [call()] 79 self.fooSource.assert_has_calls(testCalls) 80 81 # ... continued in the next listing
OrderTest類有五個測試例程。全部五個測試例程在開始的時候都建立了一個Order類的實例。例程testA_newOrder()測試Order對象是否可用是否有正確的數據(46~60行)。例程testB_nilWarehouse()建立了一個空的mock並傳入Order對象的fill()方法(64~79行)。它檢查了mock的調用歷史,確保僅僅發生了工廠調用。
例程testC_orderCheck()(Listing Twenty-six)測試了Order對象在庫存不足時的反應。最初,fooSource的hasInventory()方法響應True,getinventory()方法返回0。測試例程檢查是否訂單未達成,是否正確的mock方法被帶調用(16~19行)。而後測試例程建立了一個新的Order對象,此次是一個不一樣的項目。mock(fooSource)的方法hasInventory()的響應設置爲False(第27行)。再次,例程檢查是否訂單未達成,是否調用了正確的mock方法(34~37行)。注意使用reset_mock()方法將fooSource恢復到測試前的狀態(第28行)。
Listing Twenty-six
1 class OrderTest(unittest.TestCase): 2 # ... see previous listing 3 4 # test: checking the inventory 5 # objective: does the order object check for inventory? 6 def testC_orderCheck(self): 7 """Test routine C""" 8 # creating a test order 9 testOrder = Order("mushrooms", 10) 10 print repr(testOrder) 11 12 # perform the test 13 testOrder.fill(self.fooSource) 14 15 # perform the checks 16 self.assertFalse(testOrder.isFilled()) 17 self.assertEqual(testOrder._orderFilled, 0) 18 19 self.fooSource.hasInventory.assert_called_once_with("mushrooms") 20 print self.fooSource.mock_calls 21 22 # creating another order 23 testOrder = Order("cabbage", 10) 24 print repr(testOrder) 25 26 # reconfigure the test resource 27 self.fooSource.hasInventory.return_value = False 28 self.fooSource.reset_mock() 29 30 # perform the test 31 testOrder.fill(self.fooSource) 32 33 # perform the checks 34 self.assertFalse(testOrder.isFilled()) 35 self.assertEqual(testOrder._orderFilled, -1) 36 37 self.fooSource.hasInventory.assert_called_once_with("cabbage") 38 print self.fooSource.mock_calls 39 40 # ... continued in the next listing
測試例程testD_orderFilled()(Listing Twenty-seven)模擬了一個成功的訂單事務。fooSource的hasInventory()方法響應True,getinventory()方法返回10。例程調用fill()方法傳入mock對象,而後檢查訂單是否已完成(17~18行)。它也檢查了是否採用正確的順序和正確的參數調用了 正確的mock方法(20~24行)。
Listing Twenty-seven
1 class OrderTest(unittest.TestCase): 2 # ... see previous listing 3 4 # test: fulfilling an order 5 # objective: how does the order object behave with a successful transaction 6 def testD_orderFilled(self): 7 """Test routine D""" 8 # creating a test order 9 testOrder = Order("mushrooms", 10) 10 print repr(testOrder) 11 12 # perform the test 13 testOrder.fill(self.fooSource) 14 print testOrder.isFilled() 15 16 # perform the checks 17 self.assertTrue(testOrder.isFilled()) 18 self.assertNotEqual(testOrder._orderFilled, -1) 19 20 self.fooSource.hasInventory.assert_called_once_with("mushrooms") 21 self.fooSource.getInventory.assert_called_with("mushrooms", 10) 22 23 testCalls = [call.hasInventory("mushrooms"), call.getInventory("mushrooms", 10)] 24 self.fooSource.assert_has_calls(testCalls) 25 26 # ... continued in the next listing
測試例程testE_orderIncomplete()(Listing Twenty-eight)模擬了一個未完成的事務。在這個測試中,fooSource的方法hasInventory()響應True,可是getinventory()返回5。例程調用fill()方法傳入mock對象,而後檢查未完成的訂單(17~18行)。 它也檢查了是否採用正確的順序和正確的參數調用了正確的mock方法(20~25行)。
Listing Twenty-eight
1 class OrderTest(unittest.TestCase): 2 # ... see previous listing 3 4 # test: fulfilling an order 5 # objective: how does the order object behave with an incomplete transaction 6 def testE_orderIncomplete(self): 7 """Test routine E""" 8 # creating a test order 9 testOrder = Order("mushrooms", 10) 10 print repr(testOrder) 11 12 # perform the test 13 testOrder.fill(self.fooSource) 14 print testOrder.isFilled() 15 16 # perform the checks 17 self.assertFalse(testOrder.isFilled()) 18 self.assertNotEqual(testOrder._orderFilled, testOrder._orderAmount) 19 20 self.fooSource.hasInventory.assert_called_once_with("mushrooms") 21 self.fooSource.getInventory.assert_called_with("mushrooms", 10) 22 print self.fooSource.mock_calls 23 24 testCalls = [call.hasInventory("mushrooms"), call.getInventory("mushrooms", 10)] 25 self.fooSource.assert_has_calls(testCalls)
Mocks讓咱們爲單元測試模擬了那些不可用或者是太龐大的資源。咱們能夠在運行中配置mock,在特定的測試中改變它的行爲或響應,或者讓它在恰當的時候拋出錯誤和異常。
在這篇文章中,咱們看到了在單元測試中設置mock的好處。咱們瞭解了mock和fake或者stub的區別。咱們瞭解了怎樣在Python中建立mock,怎樣管理它和用斷言跟蹤它的行爲。咱們研究了簡單的mock在基礎測試用例中的工做。隨意嘗試了這個測試設置並觀察它的結構。隨意的調整已提供的測試例程並觀察這些調整對測試的影響