您的位置:首页 > 编程语言 > Python开发

使用 Python Mock 类进行单元测试

2015-03-04 09:30 204 查看
Data type, model, or node -- these are just some of the roles a mock object might assume. But how does a mock fit inside a unit test setup?

Sometimes, you need "other" code resources for your test setup. But those resources may be unavailable, unstable, or just too unwieldy to use. You could try and find a replacement for the missing resource; or you could simulate it by creating what is known
as a mock. Mocks let us simulate resources that are either unavailable or too unwieldy for unit testing.

Creating a mock in Python is done through the Mock module. You can build a mock one-attribute-at-a-time or wholesale with a dictionary object or a class interface. You can also define how a mock behaves and check its usage during a test. Let's explore.

Sample code presented here is written in Python 2.4, unless noted otherwise.
译者信息


数据类型、模型或节点——这些都只是mock对象可承担的角色。但mock在单元测试中扮演一个什么角色呢?

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

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

除非单独说明,下面的示例代码都是用Python2.4写的。

The Test Setup

The typical test setup has at least two parts. First, there is the test subject (red), the focus of the test. It can be a method, module, or class. It may or may not return a result, but it can raise an error or exception depending on input data or internal
state (Figure 1).



Figure 1.

Second is the test case (grey), which runs alone or as part of a suite. It prepares the test subject, as well as any data or resources needed by the subject. It runs one or more test routines and checks how the test subject behaves in each test. And it collects
the test results and presents them in a concise, readable format.

Now, some test subjects need one or more resources (green) in order to function. These resources may be another class or module, or even an independent process. Whatever their nature, test resources are functional code. Their role is to support the test subject,
but they are not the focus of the test.
译者信息


测试准备

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



图1

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

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

Reasons to Mock

But there are times when a test resource is either unavailable or unsuitable. Perhaps the resource is being developed in parallel with the test subject. It may then be incomplete or too unstable to be reliable.

The resource may be too costly. If the resource is a third-party product, its high price tag can disqualify its use for testing. Setting up the resource might be complex, taking up hardware and time that could be used elsewhere. If the resource is a data source,
setting up its data set, one that mimics real-world content, can get tedious.

The resource may simply be unpredictable. A good unit test has to be repeatable, allowing you to isolate and identify a fault. But the test resource might give out stochastic results, or it might have widely varying response times. And as a result, you end
up glossing over a potential showstopper.
译者信息


使用Mock的理由

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

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

测试资源是不可预知的。一个好的单元测试是可重复的,允许你分离和识别故障。但是测试资源可能给出随机的结果,或者它会有不同的响应时间。而作为这样的结果,测试资源最终可能成为一个潜在的搅局者。
These are some of the reasons why you might want to use a mock in place of a test resource. A mock can present the same set of method interfaces as the resource to the test subject. But a mock is easier to setup, easier to manage. It can present the test subject
the same method interfaces as the actual test resource. It can deliver deterministic results, and it can be customize to suit a particular test. And it can be easily updated to reflect changes in the actual resource.

Of course, mocks are not without issues. Designing an accurate mock is difficult, especially if you have no reliable information on the test resource. You can try and find an open-source match, or you can make a guesstimate on the resource's method interface.
Whichever you choose, you can update the mock easily later on, should you get more detailed information on the preferred resource.

Too many mocks can complicate a test, making it harder for you to track down a failure. Best practice is to limit a test case to one or two mocks, or use separate test cases for each mock/subject pairing.
译者信息


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

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

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

Mocks versus Stubs versus Fakes

A mock is not the only way to mimic a test resource. Solutions like a stub or a fake are just as capable of the same service. So, how does a mock compare against these two? Why choose a mock over a stub or a fake?

Consider the stub: A stub presents a set of method interfaces to the test subject, the same set the subject would see in an actual test resource. When the test subject calls a stub method, the stub may respond with a predetermined set of results. It may raise
an error or exception, also predetermined. A stub may track its interactions with the test subject, but it performs no tasks outside the narrow scope of its programming.

A fake also presents a set of method interfaces and tracks its interactions with the test subject. But unlike a stub, a fake actually processes input data from the test subject and produces results based on that data. In short, a fake is a functional, but non-production
version of the actual test resource. It lacks the checks and balances found in resource, it uses simpler algorithms, and it seldom, if ever, stores or transports data.

With fakes and stubs, you can test if the test subject called the right method with the right input. You can test how the subject handles the result and how it reacts to an error or exception. These tests are known as state verification. But what if you want
to know if the test subject called the same method twice? What if you want to know if it called several methods in the proper order? Such tests are known as behavior verification, and to do them, you need mocks.
译者信息


Mocks对Stubs对Fakes

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

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

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

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

To Mock with Python

The Mock module is what you use to create and manage a mock object on Python. This module is the brainchild of Michael Foord, and it is a standard module on Python 3.0. For Python 2.4 to 2.7, however, you have to install the module yourself. You can get the
latest version of the Mock module (1.0.1) from the Python Package Index website.

The Mock module provides a handful classes on which to base your mock object. There is even a patch mechanism for altering the mock on the fly. But for now, our focus is on one class: the
Mock
class.

Figure 2 shows the basic structure of the
Mock
class (green). It derives from two parent classes:
NonCallableMock
and
CallableMixin
(grey).
NonCallableMock
defines
the routines needed by the mock object. It overrides several magic methods, giving them default behaviors. And it supplies the assert routines for tracking the mock's behavior. As for
CallableMixin
, it updates those
magic methods that make a mock object callable. In turn, both parents derive from the
Base
class (red), which declares the properties needed by the mock object.



Figure 2.
译者信息


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



图2

Preparing to Mock

The
Mock
class has four sets of methods (Figure 3). In the first set is the class constructor, which takes up to six optional and labeled arguments. The diagram shows the four arguments you will use most often.



Figure 3.

The first constructor argument is the
name
argument, which gives a mock object a unique name. Listing One shows how I create a mock object (
mockFoo
) with the name
Foo
.
Notice the name appears next to the mock's unique ID when I print the mock object (lines 6-9).

Listing One
from mock import Mock

# create the mock object
mockFoo = Mock(name = "Foo")

print mockFoo
# returns: <Mock name='Foo' id='494864'>
print repr(mockFoo)
# still returns: <Mock name='Foo' id='494864'>


The next constructor argument is the
spec
argument. It sets the mock object's attributes — which may be a property or a method. The attributes can be a list of strings or can come from another Python class.

To demonstrate, in Listing Two, I have a list object (
fooSpec
) with three items (line 4): the property attribute
_fooValue
, and the method attributes
callFoo
and
doFoo
.
When I pass
fooSpec
to the class constructor (line 7),
mockFoo
gains three attributes, which I can access with the dot operator (lines 10-15). But if I access an undeclared
attribute,
mockFoo
raises an
AttributeError
and names the "faulty" attribute (lines 21-24).

Listing Two
from mock import Mock

# prepare the spec list
fooSpec = ["_fooValue", "callFoo", "doFoo"]

# create the mock object
mockFoo = Mock(spec = fooSpec)

# accessing the mocked attributes
print mockFoo
# <Mock id='427280'>
print mockFoo._fooValue
# returns <Mock name='mock._fooValue' id='2788112'>
print mockFoo.callFoo()
# returns: <Mock name='mock.callFoo()' id='2815376'>

mockFoo.callFoo()
# nothing happens, which is fine

# accessing the missing attributes
print mockFoo._fooBar
# raises: AttributeError: Mock object has no attribute '_fooBar'
mockFoo.callFoobar()
# raises: AttributeError: Mock object has no attribute 'callFoobar'

译者信息


准备Mock

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



图3

构造器的第一个参数是name,它定义了mock对象的唯一标示符。Listing one显示了怎么创建一个标示符为Foo的mock对象mockFoo。请注意当我打印mock对象(6-9行)时,标示符后紧跟的是mock对象的唯一ID。

Listing One
from mock import Mock

# create the mock object
mockFoo = Mock(name = "Foo")

print mockFoo
# returns: <Mock name='Foo' id='494864'>
print repr(mockFoo)
# still returns: <Mock name='Foo' id='494864'>


构造器的第二个参数是spec。它设置mock对象的属性,可以是property或者方法。属性可以是一个列表字符串或者是其他的Python类。

为了演示,在Listing Two中,我有一个带三个项目的列表对象fooSpec(第4行):property属性_fooValue,方法属性callFoo和doFoo。当我把fooSpec带入类构造器时(第7行),mockFoo获得了三个属性,我能用点操作符访问它们(10~15行)。当我访问了一个未声明的属性时,mockFoo引发AttributeError和"faulty"属性(21~14行)。

Listing Two

from mock import Mock

# prepare the spec list
fooSpec = ["_fooValue", "callFoo", "doFoo"]

# create the mock object
mockFoo = Mock(spec = fooSpec)

# accessing the mocked attributes
print mockFoo
# <Mock id='427280'>
print mockFoo._fooValue
# returns <Mock name='mock._fooValue' id='2788112'>
print mockFoo.callFoo()
# returns: <Mock name='mock.callFoo()' id='2815376'>

mockFoo.callFoo()
# nothing happens, which is fine

# accessing the missing attributes
print mockFoo._fooBar
# raises: AttributeError: Mock object has no attribute '_fooBar'
mockFoo.callFoobar()
# raises: AttributeError: Mock object has no attribute 'callFoobar'

Listing Three shows another use of the
spec
argument. This time, I have class
Foo
with the same three attributes (lines 4-12). I pass the class name to the constructor
(line 15), which then produces a mock object with the same attributes as
Foo
(lines 18-23). Again, accessing an undeclared attribute raises an
AttributeError
(lines 29-32).
Also, in both examples, the method attributes are non-functional. Calling a mocked method does nothing, even if said method has functional code (lines 25-26).

Listing Three
from mock import Mock

# The class interfaces
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# create the mock object
mockFoo = Mock(spec = Foo)

# accessing the mocked attributes
print mockFoo
# returns <Mock spec='Foo' id='507120'>
print mockFoo._fooValue
# returns <Mock name='mock._fooValue' id='2788112'>
print mockFoo.callFoo()
# returns: <Mock name='mock.callFoo()' id='2815376'>

mockFoo.callFoo()
# nothing happens, which is fine

# accessing the missing attributes
print mockFoo._fooBar
# raises: AttributeError: Mock object has no attribute '_fooBar'
mockFoo.callFoobar()
# raises: AttributeError: Mock object has no attribute 'callFoobar'


The next constructor argument is
return_value
. This one sets a mock object's response when it gets a direct call. I use this argument for simulating a factory call.

To demonstrate, in Listing Four, I have
return_value
set to
456
(line 4). When I call
mockFoo
, I get
456
for
a result (line 9-11). In Listing Five, I pass an instance of class
Foo
(
fooObj
) for
return_value
(lines 15-19). Now, when
I call
mockFoo
, I get
fooObj
instead (shown here as
mockObj
) (line 24). And unlike in Listings Two and Three, the methods
in
mockObj
are functional.

Listing Four
from mock import Mock

# create the mock object
mockFoo = Mock(return_value = 456)

print mockFoo
# <Mock id='2787568'>

mockObj = mockFoo()
print mockObj
# returns: 456


Listing Five
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# creating the mock object
fooObj = Foo()
print fooObj
# returns: <__main__.Foo object at 0x68550>

mockFoo = Mock(return_value = fooObj)
print mockFoo
# returns: <Mock id='2788144'>

# creating an "instance"
mockObj = mockFoo()
print mockObj
# returns: <__main__.Foo object at 0x68550>

# working with the mocked instance
print mockObj._fooValue
# returns: 123
mockObj.callFoo()
# returns: Foo:callFoo_
mockObj.doFoo("narf")
# returns: Foo:doFoo:input =  narf
<Mock id='428560'>


Counter to
return_value
is the
side_effect
argument. This one assigns the mock with an alternative result, one that overrides
return_value
.
In short, a simulated factory call returns the
side_effect
value, not the
return_value
.

Listing Six demonstrates the effects of the
side_effect
argument. First, I create an instance of class
Foo
(
fooObj
) and pass
it as a
return_value
argument (line 17). The result is similar to Listing Five;
mockFoo
returns
fooObj
when called (line 22).
But then I repeat the same steps, passing
StandardError
for the
side_effect
argument (line 28). Now, calling
mockFoo
raises
StandardError
,
instead of returning
fooObj
(lines 29-30).
译者信息


Listing Three显示了spec参数的另一种用法。这次,有带三个属性的类Foo(4~12行)。把类名传入构造器中,这样就产生了一个和Foo有相同属性的mock对象(18~23行)。再次,访问一个未声明的属性引发了AttributeError(29~32行)。也就是说,在两个例子中,方法属性时没有功能的。甚至在方法有功能代码时,调用mock的方法也什么都不做。

Listing Three

from mock import Mock

# The class interfaces
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# create the mock object
mockFoo = Mock(spec = Foo)

# accessing the mocked attributes
print mockFoo
# returns <Mock spec='Foo' id='507120'>
print mockFoo._fooValue
# returns <Mock name='mock._fooValue' id='2788112'>
print mockFoo.callFoo()
# returns: <Mock name='mock.callFoo()' id='2815376'>

mockFoo.callFoo()
# nothing happens, which is fine

# accessing the missing attributes
print mockFoo._fooBar
# raises: AttributeError: Mock object has no attribute '_fooBar'
mockFoo.callFoobar()
# 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

from mock import Mock

# create the mock object
mockFoo = Mock(return_value = 456)

print mockFoo
# <Mock id='2787568'>

mockObj = mockFoo()
print mockObj
# returns: 456


Listing Five

from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# creating the mock object
fooObj = Foo()
print fooObj
# returns: <__main__.Foo object at 0x68550>

mockFoo = Mock(return_value = fooObj)
print mockFoo
# returns: <Mock id='2788144'>

# creating an "instance"
mockObj = mockFoo()
print mockObj
# returns: <__main__.Foo object at 0x68550>

# working with the mocked instance
print mockObj._fooValue
# returns: 123
mockObj.callFoo()
# returns: Foo:callFoo_
mockObj.doFoo("narf")
# returns: Foo:doFoo:input =  narf
<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
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# creating the mock object (without a side effect)
fooObj = Foo()

mockFoo = Mock(return_value = fooObj)
print mockFoo
# returns: <Mock id='2788144'>

# creating an "instance"
mockObj = mockFoo()
print mockObj
# returns: <__main__.Foo object at 0x2a88f0>

# creating a mock object (with a side effect)

mockFoo = Mock(return_value = fooObj,side_effect = StandardError)
mockObj = mockFoo()
# raises: StandardError


Listing Seven shows another effect. In this one, I pass a list object (
fooList
) to the class constructor (lines 17-18). Then, each time I call
mockFoo
, it returns a list
item in succession (lines 20-30). Once
mockFoo
reaches the end of the list, another call will raise a
StopIteration
error (lines 32-34).

Listing Seven
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# creating the mock object (with a side effect)
fooObj = FooSpec()

fooList = [665, 666, 667]
mockFoo = Mock(return_value = fooObj,side_effect = fooList)

fooTest = mockFoo()
print fooTest
# returns 665

fooTest = mockFoo()
print fooTest
# returns 666

fooTest = mockFoo()
print fooTest
# returns 667

fooTest = mockFoo()
print fooTest
# raises: StopIteration


You could pass other iterable objects (
set, tuple
) to the
side_effect
argument. What you cannot pass is a primitive (integer,
string, and so on) because these are not iterable. To make a primitive iterable, add it to a single-item list.
译者信息


Listing Six
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# creating the mock object (without a side effect)
fooObj = Foo()

mockFoo = Mock(return_value = fooObj)
print mockFoo
# returns: <Mock id='2788144'>

# creating an "instance"
mockObj = mockFoo()
print mockObj
# returns: <__main__.Foo object at 0x2a88f0>

# creating a mock object (with a side effect)

mockFoo = Mock(return_value = fooObj,side_effect = StandardError)
mockObj = mockFoo()
# raises: StandardError


Listing Seven显示了另一个影响。在这个例子中,传入一个列表对象fooList到类构造器中(17~18行)。然后,每次我调用mockFoo时,它连续的返回列表中的项(20~30行)。一旦mockFoo到达了列表的末尾,调用将引发StopIteration 错误(32~34行)

Listing Seven
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# creating the mock object (with a side effect)
fooObj = FooSpec()

fooList = [665, 666, 667]
mockFoo = Mock(return_value = fooObj,side_effect = fooList)

fooTest = mockFoo()
print fooTest
# returns 665

fooTest = mockFoo()
print fooTest
# returns 666

fooTest = mockFoo()
print fooTest
# returns 667

fooTest = mockFoo()
print fooTest
# raises: StopIteration


你可以传入其他的可迭代对象(集合,元组)到side_effct对象中。你不能传入一个简单对象(如整数、字符串等),因为这些对象是不能迭代的,为了让简单对象可迭代,需要将他们加入单一项的列表中。

Asserting with a Mock

The next set of methods from the
Mock
class are the asserts. These help track the method calls made to the mock by the test subject. They can work in conjunction with the asserts from the unittest module. They can
be attached to the mock or to one of its method attributes. All but one take the same two optional arguments: a variable sequence, and a key/value sequence.

The first assert,
assert_called_with()
, checks if a mocked method gets the right arguments. It fires when at least one argument has the wrong value or type, when there is a wrong number of arguments, when the arguments
are in the wrong order, or when the mocked method is not expecting any arguments at all. Listing Eight shows how you might use this assert. Here, I prepared a mock object with class
Foo
as its spec. I call the mocked
method
doFoo()
, passing a string for input. With
assert_called_with()
, I check if the method gets the right input. The assert in line 20 passes because
doFoo()
got
"narf"
for
input. But the assert in line 24 fails because
doFoo()
got
"zort"
, which is wrong.

Listing Eight
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
pass

def doFoo(self, argValue):
pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.doFoo("narf")
mockFoo.doFoo.assert_called_with("narf")
# assertion passes

mockFoo.doFoo("zort")
mockFoo.doFoo.assert_called_with("narf")
# AssertionError: Expected call: doFoo('narf')
# Actual call: doFoo('zort')

译者信息


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
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
pass

def doFoo(self, argValue):
pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.doFoo("narf")
mockFoo.doFoo.assert_called_with("narf")
# assertion passes

mockFoo.doFoo("zort")
mockFoo.doFoo.assert_called_with("narf")
# AssertionError: Expected call: doFoo('narf')
# Actual call: doFoo('zort')

Listing Nine shows a slightly different use. In this one, I call the mocked method
callFoo()
, first without input, and then with the string
"zort"
. The first assert passes
(line 20), because
callFoo()
is not supposed to get any input. And the second assert fails (line 24) for obvious reasons.

Listing Nine
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
pass

def doFoo(self, argValue):
pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.callFoo.assert_called_with()
# assertion passes

mockFoo.callFoo("zort")
mockFoo.callFoo.assert_called_with()
# AssertionError: Expected call:callFoo()
# Actual call: callFoo('zort')


The next assert is
assert_called_once_with()
. Like
assert_called_with()
, this assert checks if the test subject called a mocked method correctly. But
assert_called_once_with()
will
fire when the same method call happens more than once, whereas
assert_called_with()
will ignore multiple calls. Listing Ten shows how one might use the assert. Here, I make two calls to the mocked method
callFoo()
.
On the first call (lines 19-20), the assert passes. But on the second call (lines 23-24), the assert fires, sending its error message to
stdout
.

Listing Ten
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
pass

def doFoo(self, argValue):
pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.callFoo.assert_called_once_with()
# assertion passes

mockFoo.callFoo()
mockFoo.callFoo.assert_called_once_with()
# AssertionError: Expected to be called once. Called 2 times.


The assert
assert_any_call()
checks if the test subject called a mocked method at any point of the test routine. This is regardless of how many other calls were made between the mocked method and the assert. Compare
this with the previous two asserts, both of which check only the most recent call.

Listing Eleven shows the
assert_any_call()
assert at work: still the same mock object, with class
Foo
as its spec. The first call is to the method
callFoo()
(line
18), the next two to
doFoo()
(lines 19-20). Notice that
doFoo()
gets two different inputs.
译者信息


Listing Nine显示了稍微不同的用法。在这个例子中,我调用了mock方法callFoo(),首先没有任何输入,然后输入了字符串“zort”。第一个断言通过了(第20行),因为callFoo()不支持获得任何输入。而第二个断言失败了(第24行)因为显而易见的原因。

Listing Nine
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
pass

def doFoo(self, argValue):
pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.callFoo.assert_called_with()
# assertion passes

mockFoo.callFoo("zort")
mockFoo.callFoo.assert_called_with()
# AssertionError: Expected call:callFoo()
# 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
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
pass

def doFoo(self, argValue):
pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.callFoo.assert_called_once_with()
# assertion passes

mockFoo.callFoo()
mockFoo.callFoo.assert_called_once_with()
# 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
<from mock import Mock

# The mock specification
class Foo(object):
_fooValue = 123

def callFoo(self):
pass

def doFoo(self, argValue):
pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.doFoo("narf")
mockFoo.doFoo("zort")

mockFoo.callFoo.assert_any_call()
# assert passes

mockFoo.callFoo()
mockFoo.doFoo("troz")

mockFoo.doFoo.assert_any_call("zort")
# assert passes

mockFoo.doFoo.assert_any_call("egad")
# raises: AssertionError: doFoo('egad') call not found


The first
assert_any_call()
(line 22) passes even though two
doFoo()
calls separate the assert and
callFoo()
. The second assert
(line 28) also passes even though a
callFoo()
separates it from the
doFoo()
in question (line 20). On the other hand, the third assert (line 31) fires, because none of
the
doFoo()
calls used the string
"egad"
for input.

Finally, there is
assert_has_calls()
. This one looks at a sequence of method calls, checks if they are in the right order and with the right arguments. It takes two arguments: a list of expected method calls and
an optional argument
any_order
. It fires when the test subject calls the wrong method, calls one method out of order, or gives a method the wrong input.
译者信息


Listing Eleven
<from mock import Mock

# The mock specification
class Foo(object):
_fooValue = 123

def callFoo(self):
pass

def doFoo(self, argValue):
pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.doFoo("narf")
mockFoo.doFoo("zort")

mockFoo.callFoo.assert_any_call()
# assert passes

mockFoo.callFoo()
mockFoo.doFoo("troz")

mockFoo.doFoo.assert_any_call("zort")
# assert passes

mockFoo.doFoo.assert_any_call("egad")
# 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 demonstrates the
assert_has_calls()
assert. In lines 18-20, I make three method calls, providing input to two. Then, I prepare a list of expected calls (
fooCalls
)
and pass this list to
assert_has_calls()
(lines 22-23). Since the list matches the method calls, the assert passes.

Listing Twelve
from mock import Mock, call

# The mock specification
class Foo(object):
_fooValue = 123

def callFoo(self):
pass

def doFoo(self, argValue):
pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.doFoo("narf")
mockFoo.doFoo("zort")

fooCalls = [call.callFoo(), call.doFoo("narf"), call.doFoo("zort")]
mockFoo.assert_has_calls(fooCalls)
# assert passes

fooCalls = [call.callFoo(), call.doFoo("zort"), call.doFoo("narf")]
mockFoo.assert_has_calls(fooCalls)
# AssertionError: Calls not found.
# Expected: [call.callFoo(), call.doFoo('zort'), call.doFoo('narf')]
# Actual: [call.callFoo(), call.doFoo('narf'), call.doFoo('zort')]

fooCalls = [call.callFoo(), call.doFoo("zort"), call.doFoo("narf")]
mockFoo.assert_has_calls(fooCalls, any_order = True)
# assert passes


In line 26, I swapped the two
doFoo()
calls around. The first
doFoo()
gets
"zort"
for input, the second gets
"narf"
.
If I pass this
fooCalls
to
assert_has_calls()
(line 27), the assert fires. But if I pass a
True
to the
any_order
argument,
the assert passes. This is because the assert now ignores the order in which the method calls were made.
译者信息


Listing Twelve演示了assert_has_calls()断言。在18~20行,我调用了三个方法,提供了两个输入。然后,我准备了一个期望调用的列表(fooCalls)并把这个列表传入assert_has_calls()(22~23行)。由于列表匹配了方法的调用,断言通过。

Listing Twelve
from mock import Mock, call

# The mock specification
class Foo(object):
_fooValue = 123

def callFoo(self):
pass

def doFoo(self, argValue):
pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.doFoo("narf")
mockFoo.doFoo("zort")

fooCalls = [call.callFoo(), call.doFoo("narf"), call.doFoo("zort")]
mockFoo.assert_has_calls(fooCalls)
# assert passes

fooCalls = [call.callFoo(), call.doFoo("zort"), call.doFoo("narf")]
mockFoo.assert_has_calls(fooCalls)
# AssertionError: Calls not found.
# Expected: [call.callFoo(), call.doFoo('zort'), call.doFoo('narf')]
# Actual: [call.callFoo(), call.doFoo('narf'), call.doFoo('zort')]

fooCalls = [call.callFoo(), call.doFoo("zort"), call.doFoo("narf")]
mockFoo.assert_has_calls(fooCalls, any_order = True)
# assert passes


在第26行,我交换了两个doFoo()调用的顺序。第一个doFoo()获得"zort"的输入,第二个获得了"narf"。如果我传入这个fooCalls到assert_has_calls()(第27行)中,断言失败。但是如果我给参数any_order传入参数True,断言通过。这是因为断言将忽略方法调用的顺序。
Listing Thirteen demonstrates another use. To the list
fooCalls
, I added a nonexistent method
dooFoo()
(line 22). Then I passed
fooCalls
to
assert_has_calls(
)
(line 24). The assert fires, informing me that the expected call sequence did not match what actually happened. If I pass a
True
to the
any_order
argument (line 30), the
assert names
dooFoo()
as the offending method call.

Listing Thirteen
from mock import Mock, call

# The mock specification
class Foo(object):
_fooValue = 123

def callFoo(self):
pass

def doFoo(self, argValue):
pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.doFoo("narf")
mockFoo.doFoo("zort")

fooCalls = [call.callFoo(), call.dooFoo("narf"), call.doFoo("zort")]

mockFoo.assert_has_calls(fooCalls)
# AssertionError: Calls not found.
# Expected: [call.callFoo(), call.dooFoo('narf'), call.doFoo('zort')]
# Actual: [call.callFoo(), call.doFoo('narf'), call.doFoo('zort')]

fooCalls = [call.callFoo(), call.dooFoo("narf"), call.doFoo("zort")]
mockFoo.assert_has_calls(fooCalls, any_order = True)
# AssertionError: (call.dooFoo('narf'),) not all found in call list


In both examples for
assert_has_calls()
, note the
call
keyword that appears before each method name. This keyword is for a helper object, one that marks out a method attribute
in the mock object. To use the
call
keyword, make sure to import the helper from the mock module as follows:
from mock import Mock, call

译者信息


Listing Thirteen演示了其他的用法。在fooCalls列表中,我添加了不存在的方法dooFoo()(第22行)。然后我传入fooCalls到assert_has_calls()中(第24行)。断言失败,通知我期望调用的顺序和真实发生的顺序不匹配。如果我给any_order赋值为True(第30行),断言名称dooFoo()作为违规的方法调用。

Listing Thirteen

from mock import Mock, call

# The mock specification
class Foo(object):
_fooValue = 123

def callFoo(self):
pass

def doFoo(self, argValue):
pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.doFoo("narf")
mockFoo.doFoo("zort")

fooCalls = [call.callFoo(), call.dooFoo("narf"), call.doFoo("zort")]

mockFoo.assert_has_calls(fooCalls)
# AssertionError: Calls not found.
# Expected: [call.callFoo(), call.dooFoo('narf'), call.doFoo('zort')]
# Actual: [call.callFoo(), call.doFoo('narf'), call.doFoo('zort')]

fooCalls = [call.callFoo(), call.dooFoo("narf"), call.doFoo("zort")]
mockFoo.assert_has_calls(fooCalls, any_order = True)
# AssertionError: (call.dooFoo('narf'),) not all found in call list


在assert_has_calls()的两个例子中,注意到关键字call是出现在每个方法的前面。这个关键字是一个helper对象,标记出mock对象的方法属性。为了使用call关键字,请确保使用如下的方法从mocke模块导入helper:

from mock import Mock, call

Managing a Mock

A third set of methods from the
Mock
class allow you to control and manage your mock object. You can change how the mock behaves, alter some of its attributes, or restore the mock to its pre-test state. You can even
change the response values for each mocked method or for the mock itself.

The method
attach_mock()
lets you add a second mock object to your mock. This method takes two arguments: the second mock object (
aMock
) and an attribute name (
aName
).

Listing Fourteen demonstrates how this is done. Here, I create two mock objects,
mockFoo
and
mockBar
, each one with a different spec (lines 25, 30). To
mockFoo
,
I add
mockBar
using
attach_mock()
and the name "f
ooBar
" (line 35). Once that is done, I can access the second mock and
its attributes via the property
fooBar
(lines 46-53). And I can still access the attributes for the first mock,
mockFoo
(lines 40-43).

Listing Fourteen
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

class Bar(object):
# instance properties
_barValue = 456

def callBar(self):
pass

def doBar(self, argValue):
pass

# create the first mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

# create the second mock object
mockBar = Mock(spec = Bar)
print mockBar
# returns: <Mock spec='Bar' id='2784400'>

# attach the second mock to the first
mockFoo.attach_mock(mockBar, 'fooBar')

# access the first mock's attributes
print mockFoo
# returns: <Mock spec='Foo' id='495312'>
print mockFoo._fooValue
# returns: <Mock name='mock._fooValue' id='428976'>
print mockFoo.callFoo()
# returns: <Mock name='mock.callFoo()' id='448144'>

# access the second mock and its attributes
print mockFoo.fooBar
# returns: <Mock name='mock.fooBar' spec='Bar' id='2788592'>
print mockFoo.fooBar._barValue
# returns: <Mock name='mock.fooBar._barValue' id='2788016'>
print mockFoo.fooBar.callBar()
# returns: <Mock name='mock.fooBar.callBar()' id='2819344'>
print mockFoo.fooBar.doBar("narf")
# returns: <Mock name='mock.fooBar.doBar()' id='4544528'>


The method
configure_mock()
lets you make wholesale changes to the mock object. Its sole argument is a sequence of key/value pairs, each key being the attribute you want changed. If the mock does not have the specified
attribute,
configure_mock()
will add the attribute to the mock.
译者信息


管理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

from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

class Bar(object):
# instance properties
_barValue = 456

def callBar(self):
pass

def doBar(self, argValue):
pass

# create the first mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

# create the second mock object
mockBar = Mock(spec = Bar)
print mockBar
# returns: <Mock spec='Bar' id='2784400'>

# attach the second mock to the first
mockFoo.attach_mock(mockBar, 'fooBar')

# access the first mock's attributes
print mockFoo
# returns: <Mock spec='Foo' id='495312'>
print mockFoo._fooValue
# returns: <Mock name='mock._fooValue' id='428976'>
print mockFoo.callFoo()
# returns: <Mock name='mock.callFoo()' id='448144'>

# access the second mock and its attributes
print mockFoo.fooBar
# returns: <Mock name='mock.fooBar' spec='Bar' id='2788592'>
print mockFoo.fooBar._barValue
# returns: <Mock name='mock.fooBar._barValue' id='2788016'>
print mockFoo.fooBar.callBar()
# returns: <Mock name='mock.fooBar.callBar()' id='2819344'>
print mockFoo.fooBar.doBar("narf")
# returns: <Mock name='mock.fooBar.doBar()' id='4544528'>


configure_mock()方法让你批量的更改mock对象。它唯一的参数是一个键值对序列,每个键就是你想要修改的属性。如果你的对象没有指定的属性,configure_mock()将在mock中添加属性。

Listing Fifteen shows the
configure_mock()
method in action. Once again, I have a mock object (
mockFoo
) with class
Foo
for
a spec and
555
for a
return_value
(line 13). Then with
configure_mock()
, I changed the
return_value
property
to
999
(line 17). When I call
mockFoo
directly, I get
999
for a result, instead of the original
555
.

Listing Fifteen
from mock import Mock

class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

mockFoo = Mock(spec = Foo, return_value = 555)
print mockFoo()
# returns: 555

mockFoo.configure_mock(return_value = 999)
print mockFoo()
# returns: 999

fooSpec = {'callFoo.return_value':"narf", 'doFoo.return_value':"zort", 'doFoo.side_effect':StandardError}
mockFoo.configure_mock(**fooSpec)

print mockFoo.callFoo()
# returns: narf
print mockFoo.doFoo("narf")
# raises: StandardError

fooSpec = {'doFoo.side_effect':None}
mockFoo.configure_mock(**fooSpec)
print mockFoo.doFoo("narf")
# returns: zort


Next, I prepare a dictionary object (
fooSpec
) into which I set the return values for two mocked methods and the side effect for
doFoo()
(line 21). I pass
fooSpec
into
configure_mock()
,
taking care to prefix
fooSpec
with
'**'
(line 22). Invoking
callFoo()
now returns
"narf"
as
a result; and invoking
doFoo()
, regardless of input, raises a
StandardError
signal (lines 24-27). If I alter
fooSpec
, setting
the side-effect value for
doFoo()
to
None
, I get a result of
"zort"
when invoking
doFoo()
(lines
29-32).
译者信息


Listing fifteen显示了configure_mock()方法的运用。再次,我定义了一个spec为类Foo和return_value为555的mock对象mockFoo(第13行)。然后使用configure_mock()方法更改return_value为999(第17行)。当我直接调用mockFoo时,获得的结果为999,替换了原来的555。

Listing Fifteen
from mock import Mock

class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

mockFoo = Mock(spec = Foo, return_value = 555)
print mockFoo()
# returns: 555

mockFoo.configure_mock(return_value = 999)
print mockFoo()
# returns: 999

fooSpec = {'callFoo.return_value':"narf", 'doFoo.return_value':"zort", 'doFoo.side_effect':StandardError}
mockFoo.configure_mock(**fooSpec)

print mockFoo.callFoo()
# returns: narf
print mockFoo.doFoo("narf")
# raises: StandardError

fooSpec = {'doFoo.side_effect':None}
mockFoo.configure_mock(**fooSpec)
print mockFoo.doFoo("narf")
# 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行)。
The next method,
mock_add_spec()
, lets you add new attributes to the mock object. Its function is similar to the constructor argument spec, except
mock_add_spec()
works
on an existing object, and it "erases" those attributes set by the constructor. The method takes two arguments: the attribute spec (
aSpec
) and a
spec_set
flag (
aFlag
).
Again, the spec may be a list of strings or it may be a class. The added attributes are read-only by default, but passing a
True
to the
spec_set
flag make those same
attributes writable.

Listing Sixteen demonstrates
mock_add_spec()
in action. The mock object (
mockFoo
) starts with attributes coming from class
Foo
(line
25). When I access two of the attributes (
_fooValue
and
callFoo()
), I get a result confirming their presence (lines 29-32)

Listing Sixteen
from mock import Mock

# The class interfaces
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

class Bar(object):
# instance properties
_barValue = 456

def callBar(self):
pass

def doBar(self, argValue):
pass

# create the mock object
mockFoo = Mock(spec = Foo)

print mockFoo
# returns <Mock spec='Foo' id='507120'>
print mockFoo._fooValue
# returns <Mock name='mock._fooValue' id='2788112'>
print mockFoo.callFoo()
# returns: <Mock name='mock.callFoo()' id='2815376'>

# add a new spec attributes
mockFoo.mock_add_spec(Bar)

print mockFoo
# returns: <Mock spec='Bar' id='491088'>
print mockFoo._barValue
# returns: <Mock name='mock._barValue' id='2815120'>
print mockFoo.callBar()
# returns: <Mock name='mock.callBar()' id='4544368'>

print mockFoo._fooValue
# raises: AttributeError: Mock object has no attribute '_fooValue'
print mockFoo.callFoo()
# raises: AttributeError: Mock object has no attribute 'callFoo'


Then, I use
mock_add_spec()
to add class
Bar
to
mockFoo
(line 35). The mock object now assumes the attributes declared in
class
Bar
(lines 39-42). If I access any
Foo
attribute, the mock object raises an
AttributeError
to signal their absence
(lines 44-47).
译者信息


下一个方法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
from mock import Mock

# The class interfaces
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

class Bar(object):
# instance properties
_barValue = 456

def callBar(self):
pass

def doBar(self, argValue):
pass

# create the mock object
mockFoo = Mock(spec = Foo)

print mockFoo
# returns <Mock spec='Foo' id='507120'>
print mockFoo._fooValue
# returns <Mock name='mock._fooValue' id='2788112'>
print mockFoo.callFoo()
# returns: <Mock name='mock.callFoo()' id='2815376'>

# add a new spec attributes
mockFoo.mock_add_spec(Bar)

print mockFoo
# returns: <Mock spec='Bar' id='491088'>
print mockFoo._barValue
# returns: <Mock name='mock._barValue' id='2815120'>
print mockFoo.callBar()
# returns: <Mock name='mock.callBar()' id='4544368'>

print mockFoo._fooValue
# raises: AttributeError: Mock object has no attribute '_fooValue'
print mockFoo.callFoo()
# raises: AttributeError: Mock object has no attribute 'callFoo'


然后,我使用mock_add_spec()方法添加类Bar到mockFoo(第35行)。mock对象现在的属性已声明在类Bar中(39~42行)。如果我访问任何Foo属性,mock对象将引发AttributeError 信号,表示他们不存在(44~47行)。
The last method,
resetMock()
, puts the mock object back to its pre-test state. It clears the mock's call statistics and asserts. It does not clear the
return_value
and
side_effect
properties
for both mock and its method attributes. Do this to reuse the mock and avoid the overhead of creating another mock.

Finally, you can assign a return value or side-effect to each method attribute. This you do through the accessors
return_value
and
side_effect
. For example, to make the
method
callFoo()
return a value of "
narf
", use the
return_value
accessor as follows:
mockFoo.callFoo.return_value = "narf"


To give
callFoo(
) the side-effect of
TypeError
, use the
side_effect
accessor as follows:
mockFoo.callFoo.side_effect = TypeError


To clear the side-effect, pass
None
to the accessor:
mockFoo.callFoo.side_effect = None


You can also use the same two accessors to change how the mock object responds to a factory call.
译者信息


最后一个方法resetMock(),恢复mock对象到测试前的状态。它清除了mock对象的调用统计和断言。它不会清除mock对象的return_value和side_effect属性和它的方法属性。这样做是为了重新使用mock对象避免重新创建mock的开销。

最后,你能给每个方法属性分配返回值或者side-effect。你能通过return_value和side_effect访问器做到这些。例如,按如下的语句通过return_value访问器设置方法callFoo()的返回值为"narf":
mockFoo.callFoo.return_value = "narf"


按如下的语句通过side_effect访问器 设置方法callFoo()的side-ffect为TypeError

mockFoo.callFoo.side_effect = TypeError


传入None清除side-effect

mockFoo.callFoo.side_effect = None


你也可以用这个两个相同的访问器改变mock对象对工厂调用的响应值。

Statistics with a Mock

The last set of methods consists of accessors that track any calls made to a mock object. The accessor called returns a
True
when the mock gets a factory call,
False
otherwise.
Consider the code in Listing Seventeen. After I create
mockFoo
, the called accessor returns a
False
result (lines 19-20). If I do a factory call, it returns a
True
result
(lines 22-23). But what if I create a second mock object, then invoke a mocked method (
callFoo()
, line 30)? In that case, the called accessor will only give a
False
result
(lines 31-32).

Listing Seventeen
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

print mockFoo.called
# returns: False

mockFoo()
print mockFoo.called
# returns: True

mockFoo = Mock(spec = Foo)
print mockFoo.called
# returns: False

mockFoo.callFoo()
print mockFoo.called
# returns: False


The accessor
call_count
gives the number of times a mock object gets a factory call. Consider the code in Listing Eighteen. After I create
mockFoo
,
call_count
gives
the expected result of
0
(lines 19-20). When I make a factory call to
mockFoo
,
call_count
increases by 1 (lines 22-24). When
I invoke the mocked method
callFoo()
,
call_count
remains unchanged (lines 26-28). If I do a second factory call,
call_count
should
increase by 1 more.

Listing Eighteen
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

print mockFoo.call_count
# returns: 0

mockFoo()
print mockFoo.call_count
# returns: 1

mockFoo.callFoo()
print mockFoo.call_count
# returns: 1

译者信息


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
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

print mockFoo.called
# returns: False

mockFoo()
print mockFoo.called
# returns: True

mockFoo = Mock(spec = Foo)
print mockFoo.called
# returns: False

mockFoo.callFoo()
print mockFoo.called
# 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
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

print mockFoo.call_count
# returns: 0

mockFoo()
print mockFoo.call_count
# returns: 1

mockFoo.callFoo()
print mockFoo.call_count
# returns: 1


The accessor
call_args
returns the arguments used in a factory call. Listing Nineteen demonstrates its action. For a newly created mock object (
mockFoo
), the
call_args
accessor
gives a result of
None
(lines 17-21). If I make a factory call, passing
"zort"
for input,
call_args
reports it as
"call('zort')"
(line
23-25). Note the
call
keyword in the result. For a second factory call, without input,
call_args
returns
"call()"
(lines
27-29). A third factory call, with
"troz"
for input, gives the result
"call('troz')"
from
call_args
(lines 31-33). But when
I invoke the mocked method
callFoo()
, the
call_args
accessor still returns
"call('troz')"
(lines 35-37).

Listing Nineteen
#!/usr/bin/python

from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo, return_value = "narf")
print mockFoo
# returns <Mock spec='Foo' id='507120'>
print mockFoo.call_args
# returns: None

mockFoo("zort")
print mockFoo.call_args
# returns: call('zort')

mockFoo()
print mockFoo.call_args
# returns: call()

mockFoo("troz")
print mockFoo.call_args
# returns: call('troz')

mockFoo.callFoo()
print mockFoo.call_args
# returns: call('troz')


The accessor
call_args_list
also reports the arguments used in a factory call. But while
call_args
returns the most recent arguments,
call_args_list
returns
a list, with the first item being the earliest argument. Listing Twenty shows the accessor in action, using the same code in Listing Nineteen.
译者信息


访问器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
#!/usr/bin/python

from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo, return_value = "narf")
print mockFoo
# returns <Mock spec='Foo' id='507120'>
print mockFoo.call_args
# returns: None

mockFoo("zort")
print mockFoo.call_args
# returns: call('zort')

mockFoo()
print mockFoo.call_args
# returns: call()

mockFoo("troz")
print mockFoo.call_args
# returns: call('troz')

mockFoo.callFoo()
print mockFoo.call_args
# returns: call('troz')


访问器call_args_list 也报告了工厂调用中已使用的参数。但是call_args返回最近使用的参数,而call_args_list返回一个列表,第一项为最早的参数。Listing Twenty显示了这个访问的的运用,使用了和Listing Nineteen相同的代码。
Listing Twenty
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo, return_value = "narf")
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo("zort")
print mockFoo.call_args_list
# returns: [call('zort')]

mockFoo()
print mockFoo.call_args_list
# returns: [call('zort'), call()]

mockFoo("troz")
print mockFoo.call_args_list
# returns: [call('zort'), call(), call('troz')]

mockFoo.callFoo()
print mockFoo.call_args_list
# returns: [call('zort'), call(), call('troz')]


The accessor
method_calls
reports the mocked method calls made by the test subject. Its result is a list object, each item showing the method name and its arguments.

Listing Twenty-one demonstrates
method_calls
in action. With a newly created
mockFoo
,
method_calls
returns an empty list (lines
15-19). The same also happens when I do a factory call (lines 21-23). When I invoke the mocked method
callFoo()
,
method_calls
returns a list object with one entry (lines
25-27). When I invoke
doFoo()
, passing
"narf"
for input,
method_calls
returns a list with two items (lines 29-31). Notice
how each method name appears in the order of its invocation.

Listing Twenty-one
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo, return_value = "poink")
print mockFoo
# returns <Mock spec='Foo' id='507120'>
print mockFoo.method_calls
# returns []

mockFoo()
print mockFoo.method_calls
# returns []

mockFoo.callFoo()
print mockFoo.method_calls
# returns: [call.callFoo()]

mockFoo.doFoo("narf")
print mockFoo.method_calls
# returns: [call.callFoo(), call.doFoo('narf')]

mockFoo()
print mockFoo.method_calls
# returns: [call.callFoo(), call.doFoo('narf')]


The last accessor
mock_calls
reports all calls made by the test subject to the mock object. The result is again a list, but now showing both factory and method calls. Listing Twenty-two demonstrates the accessor
in action, using the same code in Listing Twenty-one.

Listing Twenty-two
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo, return_value = "poink")
print mockFoo
# returns <Mock spec='Foo' id='507120'>

print mockFoo.mock_calls
# returns []

mockFoo()
print mockFoo.mock_calls
# returns [call()]

mockFoo.callFoo()>
print mockFoo.mock_calls
# returns: [call(), call.callFoo()]

mockFoo.doFoo("narf")
print mockFoo.mock_calls
# returns: [call(), call.callFoo(), call.doFoo('narf')]

mockFoo()
print mockFoo.mock_calls
# returns: [call(), call.callFoo(), call.doFoo('narf'), call()]

译者信息


Listing Twenty
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo, return_value = "narf")
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo("zort")
print mockFoo.call_args_list
# returns: [call('zort')]

mockFoo()
print mockFoo.call_args_list
# returns: [call('zort'), call()]

mockFoo("troz")
print mockFoo.call_args_list
# returns: [call('zort'), call(), call('troz')]

mockFoo.callFoo()
print mockFoo.call_args_list
# 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
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo, return_value = "poink")
print mockFoo
# returns <Mock spec='Foo' id='507120'>
print mockFoo.method_calls
# returns []

mockFoo()
print mockFoo.method_calls
# returns []

mockFoo.callFoo()
print mockFoo.method_calls
# returns: [call.callFoo()]

mockFoo.doFoo("narf")
print mockFoo.method_calls
# returns: [call.callFoo(), call.doFoo('narf')]

mockFoo()
print mockFoo.method_calls
# returns: [call.callFoo(), call.doFoo('narf')]


最后一个访问器mock_calls报告了测试对象对mock对象所有的调用。结果是一个列表,但是工厂调用和方法调用都显示了。Listing Twenty-two演示这个访问器的运用,使用了和Listing Twenty-one相同的代码

Listing Twenty-two
from mock import Mock

# The mock object
class Foo(object):
# instance properties
_fooValue = 123

def callFoo(self):
print "Foo:callFoo_"

def doFoo(self, argValue):
print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo, return_value = "poink")
print mockFoo
# returns <Mock spec='Foo' id='507120'>

print mockFoo.mock_calls
# returns []

mockFoo()
print mockFoo.mock_calls
# returns [call()]

mockFoo.callFoo()>
print mockFoo.mock_calls
# returns: [call(), call.callFoo()]

mockFoo.doFoo("narf")
print mockFoo.mock_calls
# returns: [call(), call.callFoo(), call.doFoo('narf')]

mockFoo()
print mockFoo.mock_calls
# returns: [call(), call.callFoo(), call.doFoo('narf'), call()]


Testing with a Mock

Data type, model, or node — these are just some of the roles a mock object might assume. But how does a mock fit inside a unit test setup? Let us take a look, using a simplified setup taken from the Martin Fowler articleMocks
Aren't Stubs.

In the test setup are three classes (Figure 4). The
Order
class is the test subject. It models a single item purchase order, which it fills from a data source. The
Warehouse
class
is the test resource. It contains a sequence of key/value pairs, the key being the item name, the value being the available quantity. And the
OrderTest
class is the test case itself.



Figure 4.

Listing Twenty-three describes the
Order
class. The class declares three properties: the item name (
_orderItem
), the requested quantity (
_orderAmount
)
and the filled quantity (
_orderFilled
). Its constructor takes two arguments (lines 8-18), which it uses to populate the properties
_orderItem
and
_orderAmount
.
Its
__repr__()
method returns a summary of the purchase order (lines 21-24).

Listing Twenty-three
class Order(object):
# instance properties
_orderItem = "None"
_orderAmount = 0
_orderFilled = -1

# Constructor
def __init__(self, argItem, argAmount):
print "Order:__init__"

# set the order item
if (isinstance(argItem, str)):
if (len(argItem) > 0):
self._orderItem = argItem

# set the order amount
if (argAmount > 0):
self._orderAmount = argAmount

# Magic methods
def __repr__(self):
# assemble the dictionary
locOrder = {'item':self._orderItem, 'amount':self._orderAmount}
return repr(locOrder)

# Instance methods
# attempt to fill the order
def fill(self, argSrc):
print "Order:fill_"

try:
# does the warehouse has the item in stock?
if (argSrc is not None):
if (argSrc.hasInventory(self._orderItem)):
# get the item
locCount =    argSrc.getInventory(self._orderItem, self._orderAmount)

# update the following property
self._orderFilled = locCount
else:
print "Inventory item not available"
else:
print "Warehouse not available"
except TypeError:
print "Invalid warehouse"

# check if the order has been filled
def isFilled(self):
print "Order:isFilled_"
return (self._orderAmount == self._orderFilled)


The
Order
class defines two instance methods. The
fill(
) method gets a data source (
argSrc
) for an argument. It checks if
the source is valid and if it has the item in question (line 33-34). It submits a withdrawal request and updates
_orderFilled
with the actual quantity withdrawn (lines 36-39). The
isFilled()
method
returns a
True
when both
_orderAmount
and
_orderFilled
have the same values (lines 48-50).
译者信息


在测试中使用MOCK

数据类型,模型或者节点,这些是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
class Order(object):
# instance properties
_orderItem = "None"
_orderAmount = 0
_orderFilled = -1

# Constructor
def __init__(self, argItem, argAmount):
print "Order:__init__"

# set the order item
if (isinstance(argItem, str)):
if (len(argItem) > 0):
self._orderItem = argItem

# set the order amount
if (argAmount > 0):
self._orderAmount = argAmount

# Magic methods
def __repr__(self):
# assemble the dictionary
locOrder = {'item':self._orderItem, 'amount':self._orderAmount}
return repr(locOrder)

# Instance methods
# attempt to fill the order
def fill(self, argSrc):
print "Order:fill_"

try:
# does the warehouse has the item in stock?
if (argSrc is not None):
if (argSrc.hasInventory(self._orderItem)):
# get the item
locCount =    argSrc.getInventory(self._orderItem, self._orderAmount)

# update the following property
self._orderFilled = locCount
else:
print "Inventory item not available"
else:
print "Warehouse not available"
except TypeError:
print "Invalid warehouse"

# check if the order has been filled
def isFilled(self):
print "Order:isFilled_"
return (self._orderAmount == self._orderFilled)


Order类定义了两个实例方法。fill()方法从参数(argSrc)中获取数据源。它检查数据源是否可用,数据源的项目是否存在问题(33~34行)。它提交了一个申请并用实际返回的数量更新_orderFilled(36~39行)。当_orderAmount和_orderFilled有相同的值时,isFilled()方法返回True(48~50行)。
Listing Twenty-four describes the
Warehouse
class. It is an abstract class, declaring properties and method interfaces, but not defining the methods themselves. The property
_houseName
holds
the warehouse's name, while
_houseList
holds its inventory. Accessors exists for these two properties.

Listing Twenty-four
class Warehouse(object):
# private properties
_houseName = None
_houseList = None

# accessors
def warehouseName(self):
return (self._houseName)

def inventory(self):
return (self._houseList)

# -- INVENTORY ACTIONS
# set up the warehouse
def setup(self, argName, argList):
	pass

# check for an inventory item
def hasInventory(self, argItem):
pass

# retrieve an inventory item
def getInventory(self, argItem, argCount):
pass

# add an inventory item
def addInventory(self, argItem, argCount):
pass


The class declares four method interfaces. The method
setup()
takes two arguments, which are meant to update the two properties. The method
hasInventory()
gets an item
name and returns a
True
if said item is in the inventory. The method
getInventory()
gets an item name and quantity. It tries to deduct that quantity from the inventory,
and returns what was deducted successfully. The method
addInventory()
also gets an item name and quantity. It is suppose to update
_houseList
with those two arguments.

In Listing Twenty-five is the test case itself,
OrderTest
. Its one property,
fooSource
, holds the mock object needed by the
Order
class.
The
setUp()
method identifies the test routine to be executed (lines 14-16), then creates and configures the mock object (lines 21-34). And the
tearDown()
method prints
an empty line to
stdout
.

Listing Twenty-five
import unittest
from mock import Mock, call

class OrderTest(unittest.TestCase):
# declare the test resource
fooSource = None

# preparing to test
def setUp(self):
""" Setting up for the test """
print "OrderTest:setUp_:begin"

# identify the test routine
testName = self.id().split(".")
testName = testName[2]
print testName

# prepare and configure the test resource
if (testName == "testA_newOrder"):
print "OrderTest:setup_:testA_newOrder:RESERVED"
elif (testName == "testB_nilInventory"):
self.fooSource = Mock(spec = Warehouse, return_value = None)
elif (testName == "testC_orderCheck"):
self.fooSource = Mock(spec = Warehouse)
self.fooSource.hasInventory.return_value = True
self.fooSource.getInventory.return_value = 0
elif (testName == "testD_orderFilled"):
self.fooSource = Mock(spec = Warehouse)
self.fooSource.hasInventory.return_value = True
self.fooSource.getInventory.return_value = 10
elif (testName == "testE_orderIncomplete"):
self.fooSource = Mock(spec = Warehouse)
self.fooSource.hasInventory.return_value = True
self.fooSource.getInventory.return_value = 5
else:
print "UNSUPPORTED TEST ROUTINE"

# ending the test
def tearDown(self):
"""Cleaning up after the test"""
print "OrderTest:tearDown_:begin"
print ""

# test: new order
# objective: creating an order
def testA_newOrder(self):
# creating a new order
testOrder = Order("mushrooms", 10)
print repr(testOrder)

# test for a nil object
self.assertIsNotNone(testOrder, "Order object is a nil.")

# test for a valid item name
testName = testOrder._orderItem
self.assertEqual(testName, "mushrooms", "Invalid item name")

# test for a valid item amount
testAmount = testOrder._orderAmount
self.assertGreater(testAmount, 0, "Invalid item amount")

# test: nil inventory
# objective: how the order object handles a nil inventory
def testB_nilInventory(self):
"""Test routine B"""
# creating a new order
testOrder = Order("mushrooms", 10)
print repr(testOrder)

# fill the order
testSource = self.fooSource()
testOrder.fill(testSource)

# print the mocked calls
print self.fooSource.mock_calls

# check the call history
testCalls = [call()]
self.fooSource.assert_has_calls(testCalls)

# ... continued in the next listing


The
OrderTest
class has five test routines. All five start by creating an instance of the
Order
class. The routine
testA_newOrder()
tests
if the
Order
object is valid and if it holds the right data (lines 46-60). The routine
testB_nilWarehouse()
creates a null mock and passes that to the
Order
object's
fill()
method
(lines 64-79). It checks the mock's call history, making sure only a factory call has occurred.
译者信息


Listing Twenty-four描述了Warehouse类。它是一个抽象类,声明了属性和方法接口,但是没有定义方法本身。属性_houseName是仓库的名字,而_houseList是它持有的库存。还有这两个属性的访问器。

Listing Twenty-four
class Warehouse(object):
# private properties
_houseName = None
_houseList = None

# accessors
def warehouseName(self):
return (self._houseName)

def inventory(self):
return (self._houseList)

# -- INVENTORY ACTIONS
# set up the warehouse
def setup(self, argName, argList):
	pass

# check for an inventory item
def hasInventory(self, argItem):
pass

# retrieve an inventory item
def getInventory(self, argItem, argCount):
pass

# add an inventory item
def addInventory(self, argItem, argCount):
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
import unittest
from mock import Mock, call

class OrderTest(unittest.TestCase):
# declare the test resource
fooSource = None

# preparing to test
def setUp(self):
""" Setting up for the test """
print "OrderTest:setUp_:begin"

# identify the test routine
testName = self.id().split(".")
testName = testName[2]
print testName

# prepare and configure the test resource
if (testName == "testA_newOrder"):
print "OrderTest:setup_:testA_newOrder:RESERVED"
elif (testName == "testB_nilInventory"):
self.fooSource = Mock(spec = Warehouse, return_value = None)
elif (testName == "testC_orderCheck"):
self.fooSource = Mock(spec = Warehouse)
self.fooSource.hasInventory.return_value = True
self.fooSource.getInventory.return_value = 0
elif (testName == "testD_orderFilled"):
self.fooSource = Mock(spec = Warehouse)
self.fooSource.hasInventory.return_value = True
self.fooSource.getInventory.return_value = 10
elif (testName == "testE_orderIncomplete"):
self.fooSource = Mock(spec = Warehouse)
self.fooSource.hasInventory.return_value = True
self.fooSource.getInventory.return_value = 5
else:
print "UNSUPPORTED TEST ROUTINE"

# ending the test
def tearDown(self):
"""Cleaning up after the test"""
print "OrderTest:tearDown_:begin"
print ""

# test: new order
# objective: creating an order
def testA_newOrder(self):
# creating a new order
testOrder = Order("mushrooms", 10)
print repr(testOrder)

# test for a nil object
self.assertIsNotNone(testOrder, "Order object is a nil.")

# test for a valid item name
testName = testOrder._orderItem
self.assertEqual(testName, "mushrooms", "Invalid item name")

# test for a valid item amount
testAmount = testOrder._orderAmount
self.assertGreater(testAmount, 0, "Invalid item amount")

# test: nil inventory
# objective: how the order object handles a nil inventory
def testB_nilInventory(self):
"""Test routine B"""
# creating a new order
testOrder = Order("mushrooms", 10)
print repr(testOrder)

# fill the order
testSource = self.fooSource()
testOrder.fill(testSource)

# print the mocked calls
print self.fooSource.mock_calls

# check the call history
testCalls = [call()]
self.fooSource.assert_has_calls(testCalls)

# ... continued in the next listing


OrderTest类有五个测试例程。所有五个测试例程在开始的时候都创建了一个Order类的实例。例程testA_newOrder()测试Order对象是否可用是否有正确的数据(46~60行)。例程testB_nilWarehouse()创建了一个空的mock并传入Order对象的fill()方法(64~79行)。它检查了mock的调用历史,确保仅仅发生了工厂调用。
The routine
testC_orderCheck()
(Listing Twenty-six) tests how the
Order
object reacts to a missing inventory item. Initially,
fooSource
responds
with a
True
with its
hasInventory()
method and a
0
with
getinventory()
. The test
routine checks if the order is not fulfilled and if the right mocked method is called (lines 16-19). Then the routine creates a new
Order
object, this time for a different item. The mock
fooSource
is
set to respond with a
False
with its
hasInventory()
method (line 27). Again, the routine checks if the order is not filled and if the right mocked method is called (lines
34-37). Note the use of the
reset_mock()
method to restore
fooSource
to its pre-test state (line 28).

Listing Twenty-six
class OrderTest(unittest.TestCase):
# ... see previous listing

# test: checking the inventory
# objective: does the order object check for inventory?
def testC_orderCheck(self):
"""Test routine C"""
# creating a test order
testOrder = Order("mushrooms", 10)
print repr(testOrder)

# perform the test
testOrder.fill(self.fooSource)

# perform the checks
self.assertFalse(testOrder.isFilled())
self.assertEqual(testOrder._orderFilled, 0)

self.fooSource.hasInventory.assert_called_once_with("mushrooms")
print self.fooSource.mock_calls

# creating another order
testOrder = Order("cabbage", 10)
print repr(testOrder)

# reconfigure the test resource
self.fooSource.hasInventory.return_value = False
self.fooSource.reset_mock()

# perform the test
testOrder.fill(self.fooSource)

# perform the checks
self.assertFalse(testOrder.isFilled())
self.assertEqual(testOrder._orderFilled, -1)

self.fooSource.hasInventory.assert_called_once_with("cabbage")
print self.fooSource.mock_calls

# ... continued in the next listing


Test routine
testD_orderFilled()
(Listing Twenty-seven) simulates a successful order transaction. Here,
fooSource
responds with a
True
with
its
hasInventory()
method and returns
10
with
getinventory()
. The routine passes the mock to the
fill()
method,
then checks if the order has been filled (lines 17-18). It also checks whether the right mocked methods were called in the right order and with the right arguments (lines 20-24).

Listing Twenty-seven
class OrderTest(unittest.TestCase):
# ... see previous listing

# test: fulfilling an order
# objective: how does the order object behave with a successful transaction
def testD_orderFilled(self):
"""Test routine D"""
# creating a test order
testOrder = Order("mushrooms", 10)
print repr(testOrder)

# perform the test
testOrder.fill(self.fooSource)
print testOrder.isFilled()

# perform the checks
self.assertTrue(testOrder.isFilled())
self.assertNotEqual(testOrder._orderFilled, -1)

self.fooSource.hasInventory.assert_called_once_with("mushrooms")
self.fooSource.getInventory.assert_called_with("mushrooms", 10)

testCalls = [call.hasInventory("mushrooms"), call.getInventory("mushrooms", 10)]
self.fooSource.assert_has_calls(testCalls)

# ... continued in the next listing


Test routine
testE_orderIncomplete()
(Listing Twenty-eight) simulates an incomplete transaction. In this one,
fooSource
responds with a
True
with
its
hasInventory()
, but with a
5
with
getinventory()
. The routine passes the mock to the
fill()
method,
then checks for an incomplete order (lines 17-18). And it also checks if the right mocked methods were called in the right order and with the right arguments (lines 20-25).

Listing Twenty-eight
class OrderTest(unittest.TestCase):
# ... see previous listing

# test: fulfilling an order
# objective: how does the order object behave with an incomplete transaction
def testE_orderIncomplete(self):
"""Test routine E"""
# creating a test order
testOrder = Order("mushrooms", 10)
print repr(testOrder)

# perform the test
testOrder.fill(self.fooSource)
print testOrder.isFilled()

# perform the checks
self.assertFalse(testOrder.isFilled())
self.assertNotEqual(testOrder._orderFilled, testOrder._orderAmount)

self.fooSource.hasInventory.assert_called_once_with("mushrooms")
self.fooSource.getInventory.assert_called_with("mushrooms", 10)
print self.fooSource.mock_calls

testCalls = [call.hasInventory("mushrooms"), call.getInventory("mushrooms", 10)]
self.fooSource.assert_has_calls(testCalls)

译者信息


例程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
class OrderTest(unittest.TestCase):
# ... see previous listing

# test: checking the inventory
# objective: does the order object check for inventory?
def testC_orderCheck(self):
"""Test routine C"""
# creating a test order
testOrder = Order("mushrooms", 10)
print repr(testOrder)

# perform the test
testOrder.fill(self.fooSource)

# perform the checks
self.assertFalse(testOrder.isFilled())
self.assertEqual(testOrder._orderFilled, 0)

self.fooSource.hasInventory.assert_called_once_with("mushrooms")
print self.fooSource.mock_calls

# creating another order
testOrder = Order("cabbage", 10)
print repr(testOrder)

# reconfigure the test resource
self.fooSource.hasInventory.return_value = False
self.fooSource.reset_mock()

# perform the test
testOrder.fill(self.fooSource)

# perform the checks
self.assertFalse(testOrder.isFilled())
self.assertEqual(testOrder._orderFilled, -1)

self.fooSource.hasInventory.assert_called_once_with("cabbage")
print self.fooSource.mock_calls

# ... 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
class OrderTest(unittest.TestCase):
# ... see previous listing

# test: fulfilling an order
# objective: how does the order object behave with a successful transaction
def testD_orderFilled(self):
"""Test routine D"""
# creating a test order
testOrder = Order("mushrooms", 10)
print repr(testOrder)

# perform the test
testOrder.fill(self.fooSource)
print testOrder.isFilled()

# perform the checks
self.assertTrue(testOrder.isFilled())
self.assertNotEqual(testOrder._orderFilled, -1)

self.fooSource.hasInventory.assert_called_once_with("mushrooms")
self.fooSource.getInventory.assert_called_with("mushrooms", 10)

testCalls = [call.hasInventory("mushrooms"), call.getInventory("mushrooms", 10)]
self.fooSource.assert_has_calls(testCalls)

# ... 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

class OrderTest(unittest.TestCase):
# ... see previous listing

# test: fulfilling an order
# objective: how does the order object behave with an incomplete transaction
def testE_orderIncomplete(self):
"""Test routine E"""
# creating a test order
testOrder = Order("mushrooms", 10)
print repr(testOrder)

# perform the test
testOrder.fill(self.fooSource)
print testOrder.isFilled()

# perform the checks
self.assertFalse(testOrder.isFilled())
self.assertNotEqual(testOrder._orderFilled, testOrder._orderAmount)

self.fooSource.hasInventory.assert_called_once_with("mushrooms")
self.fooSource.getInventory.assert_called_with("mushrooms", 10)
print self.fooSource.mock_calls

testCalls = [call.hasInventory("mushrooms"), call.getInventory("mushrooms", 10)]
self.fooSource.assert_has_calls(testCalls)

Conclusion

Mocks let us simulate resources that are either unavailable or too unwieldy for unit testing. We can configure a mock on the fly, change how it behaves or responds in a particular test, or get it to throw errors and exceptions on cue.

In this article, we looked at how a unit test setup benefits from a mock. We learned how a mock differs from a fake or a stub. We learned how to create a mock on Python, how to manage it and how to track its behavior with asserts. And we examined a simple mock
at work in a basic test case.Feel free to try out this test setup and observe its results. Feel free to tweak the test routines presented here, and experiment with how those tweaks affect the test.

References

Alex Marandon. Python Mock Gotchas.

Grig Gheorghiu. (2009). Python Mock Testing Techniques and Tools. Python
Magazine

Insomihack. Python Unit Testing with Mock.

Matt Cottingham. Python: Injecting Mock Objects for Powerful
Testing.

Michael Foord. (2012). Mock Documentation, Release 1.0.1. [PDF]

Martin Fowler. Mocks Aren't Stubs.

Naftuli Tzvi Kay. An Introduction to Mocking in Python.

Steve Freeman, Tim Mackinnon, et al. (2004). Mock Roles, Not Objects [PDF].OOPLSA
2004
译者信息


结束语

Mocks让我们为单元测试模拟了那些不可用或者是太庞大的资源。我们可以在运行中配置mock,在特定的测试中改变它的行为或响应,或者让它在恰当的时候抛出错误和异常。

在这篇文章中,我们看到了在单元测试中设置mock的好处。我们了解了mock和fake或者stub的区别。我们了解了怎样在Python中创建mock,怎样管理它和用断言跟踪它的行为。我们研究了简单的mock在基础测试用例中的工作。随意尝试了这个测试设置并观察它的结构。随意的调整已提供的测试例程并观察这些调整对测试的影响。

引用

Alex Marandon. Python Mock Gotchas.

Grig Gheorghiu. (2009). Python
Mock Testing Techniques and Tools. Python Magazine

Insomihack. Python Unit Testing with Mock.

Matt Cottingham. Python:
Injecting Mock Objects for Powerful Testing.

Michael Foord. (2012). Mock Documentation, Release 1.0.1.
[PDF]

Martin Fowler. Mocks Aren't Stubs.

Naftuli Tzvi Kay. An Introduction to Mocking
in Python.

Steve Freeman, Tim Mackinnon, et al. (2004). Mock Roles, Not Objects [PDF]. OOPLSA
2004

http://www.oschina.net/translate/unit-testing-with-the-python-mock-class?cmp&p=3# http://www.drdobbs.com/testing/unit-testing-with-the-python-mock-class/240168251
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: