zoukankan      html  css  js  c++  java
  • 使用 Python Mock 类进行单元测试

      数据类型、模型或节点——这些都只是mock对象可承担的角色。但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在基础测试用例中的工作。随意尝试了这个测试设置并观察它的结构。随意的调整已提供的测试例程并观察这些调整对测试的影响

  • 相关阅读:
    LeetCode Find Duplicate File in System
    LeetCode 681. Next Closest Time
    LeetCode 678. Valid Parenthesis String
    LeetCode 616. Add Bold Tag in String
    LeetCode 639. Decode Ways II
    LeetCode 536. Construct Binary Tree from String
    LeetCode 539. Minimum Time Difference
    LeetCode 635. Design Log Storage System
    LeetCode Split Concatenated Strings
    LeetCode 696. Count Binary Substrings
  • 原文地址:https://www.cnblogs.com/kirago/p/4726023.html
Copyright © 2011-2022 走看看