使用 Python Mock 类进行单元测试

有时,你需要为单元测试的初始设置准备一些“其他”的代码资源。但这些资源兴许会不可用,不稳定,或者是使用起来太笨重。你可以试着找一些其他的资源替代;  你可以通过创建一个被称为mock的东西来模拟它。Mocks能够让我们模拟那些在单元测试中不可用或太笨重的资源。


在Python中创建mock是通过Mock模块完成的。你可以通过每次一个属性(one-attribute-at-a-time)或一个健全的字典对象或是一个类接口来创建mock。你还可以定义mock的行为并且在测试过程中检查它的使用。

测试准备

典型的测试准备最少有两个部分。首先是测试对象(红色),这是测试的关注点。它可以是一个方法、模块或者类。它可以返回一个结果,也可以不返回结果,但是它可以根据数据数据或者内部状态产生错误或者异常(图1)。

Python

图1

第二测试用例(灰色),它可以单独运行也可以作为套件的一部分。它是为测试对象准备的,也可以是测试对象需要的任意数据或资源。运行一个或多个测试事务,在每个测试中检查测试对象的行为。收集测试结果并用一个简洁、易读的格式呈现测试结果。

现在,为了发挥作用,一些测试对象需要一个或多个资源(绿色)。这些资源可以是其他的类或者模块,甚至是一个非独立的进程。不论其性质,测试资源是功能性的代码。他们的角色是支持测试对象,但是他们不是测试的关注点。

 

使用Mock的理由

但是有些时候,测试资源不可用,或者不适合。也许这个资源正在和测试对象并行开发中,或者并不完整或者是太不稳定以至于不可靠。

测试资源太昂贵,如果测试资源是第三方的产品,其高昂的价格不适用于测试。测试资源的建立过于复杂,占用的硬件和时间可以用于别的地方。如果测试资源是一个数据源,建立它的数据集模仿真实世界是乏味的。

测试资源是不可预知的。一个好的单元测试是可重复的,允许你分离和识别故障。但是测试资源可能给出随机的结果,或者它会有不同的响应时间。而作为这样的结果,测试资源最终可能成为一个潜在的搅局者。

 这些都是你可能想要用mock代替测试资源的原因。mock向测试对象提供一套和测试资源相同的方法接口。但是mock是更容易创建和管理。它能向测试对象提供和真实的测试资源相同的方法接口。它能提供确定的结果,并可以自定义以适用于特定的测试。能够容易的更新,以反映实际资源的变化。

当然,mocks不是没有问题的。设计一个精确的mock是困难的,特别是如果你没有测试资源的可靠信息。你可以尝试找到一个开源的接口,或者你能对测试资源的方法接口进行猜测。无论你如何选择,你都可以在以后轻松的更新mock,你可以在首选资源中得到更详细的信息。

太多的mock会使测试过于复杂,让你跟踪错误变得更困难。最好的实践是每个测试用例限制使用一到两个mock,或者为每个mock/对象对使用独立的测试用例。

 

Mocks对Stubs对Fakes

Mock不是模仿测试资源的唯一方式。其他的解决方案如stub和fake也能提供相同的服务。因此,mock和其他两种解决方案怎样比较?为什么选择mock而不是选择stub或者fake?

认识stub:stub为测试对象提供了一套方法接口,和真实的测试资源提供给测试对象的接口是相同的。当测试对象调用stub方法时,stub响应预定的结果。也可以产生一个预定的错误或者异常。stub可以跟踪和测试对象的交互,但是它不处理输入的数据。

fake也提供了一套方法接口并且也可以跟踪和测试对象的交互。但是和stub不同,fake真正的处理了从测试对象输入的数据产生的结果是基于这些数据的。简而言之,fake是功能性的,它是真实测试资源的非生产版。它缺乏资源的相互制衡,使用了更简单的算法,而且它很少存储和传输数据。

使用fake和stub,你可以输入正确的数据调用了正确的方法对测试对象进行测试。你能测试对象是如何处理数据并产生结果,当出现错误或者异常时是怎样反应的。这些测试被称为状态验证。但是你是否想知道测试对象调用了两次相同的方法?你是否想知道测试对象是否按照正确的顺序调用了几个方法?这种测试被称为行为验证,而要做到这些,你需要mocks。

 

使用Python Mock

在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对象所需的属性。

Python

图2

 

准备Mock

Mock类有四套方法(图3)。第一套方法是类的构造器,它有六个可选和已标记的参数。图中显示了4个经常用到的参数。

Python

图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类的下一套方法是断言。它将帮助跟踪测试对象对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方法或者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对象所做的任意调用的访问器。当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对象可能被假定的一些角色。但是mock对象怎样适合单元测试呢?让我们一起来看看,来自Martin Fowler的文章Mocks Aren't Stubs采取了简化的设置。

在这个测试中,设置了三个类(图4)。Order类是测试对象。它模拟了单一项目的采购订单,订单来源于一个数据源。Warehouse类是测试资源。它包含了键值对的序列,键是项目的名称,值是可用的数量。OrderTest类是测试用例本身。

Python

图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     &#9;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在基础测试用例中的工作。随意尝试了这个测试设置并观察它的结构。随意的调整已提供的测试例程并观察这些调整对测试的影响

Stay foolish!Stay hungry!

猜你喜欢

转载自blog.csdn.net/Scrat_Kong/article/details/85642218