mock python
When you’re writing robust code, tests are essential for verifying that your application logic is correct, reliable, and efficient. However, the value of your tests depends on how well they demonstrate these criteria. Obstacles such as complex logic and unpredictable dependencies make writing valuable tests difficult. The Python mock object library, unittest.mock
, can help you overcome these obstacles.
在编写健壮的代码时,测试对于验证您的应用程序逻辑正确,可靠和高效至关重要。 但是,测试的价值取决于它们证明这些标准的程度。 诸如复杂的逻辑和不可预测的依赖关系之类的障碍使编写有价值的测试变得困难。 Python模拟对象库unittest.mock
可以帮助您克服这些障碍。
By the end of this article, you’ll be able to:
到本文结尾,您将能够:
Mock
patch()
Mock
创建Python模拟对象 patch()
将模拟替换为真实对象 You’ll begin by seeing what mocking is and how it will improve your tests.
您将首先了解什么是模拟以及它将如何改善您的测试。
Free Bonus: 5 Thoughts On Python Mastery, a free course for Python developers that shows you the roadmap and the mindset you’ll need to take your Python skills to the next level.
免费奖金: 关于Python精通的5个想法 ,这是针对Python开发人员的免费课程,向您展示了将Python技能提升到新水平所需的路线图和心态。
A mock object substitutes and imitates a real object within a testing environment. It is a versatile and powerful tool for improving the quality of your tests.
模拟对象可以在测试环境中替代并模仿真实对象。 它是用于提高测试质量的多功能强大工具。
One reason to use Python mock objects is to control your code’s behavior during testing.
使用Python模拟对象的原因之一是在测试过程中控制代码的行为。
For example, if your code makes HTTP requests to external services, then your tests execute predictably only so far as the services are behaving as you expected. Sometimes, a temporary change in the behavior of these external services can cause intermittent failures within your test suite.
例如,如果您的代码向外部服务发出HTTP请求 ,则测试仅可预测地执行,只要服务的行为符合您的预期即可。 有时,这些外部服务的行为的暂时更改可能会导致测试套件中的间歇性故障。
Because of this, it would be better for you to test your code in a controlled environment. Replacing the actual request with a mock object would allow you to simulate external service outages and successful responses in a predictable way.
因此,最好在受控环境中测试代码。 用模拟对象代替实际请求将使您能够以可预测的方式模拟外部服务中断和成功的响应。
Sometimes, it is difficult to test certain areas of your codebase. Such areas include except
blocks and if
statements that are hard to satisfy. Using Python mock objects can help you control the execution path of your code to reach these areas and improve your code coverage.
有时,很难测试代码库的某些区域。 这些领域包括except
块, if
是很难满足的语句。 使用Python模拟对象可以帮助您控制代码的执行路径以到达这些区域并提高代码覆盖率 。
Another reason to use mock objects is to better understand how you’re using their real counterparts in your code. A Python mock object contains data about its usage that you can inspect such as:
使用模拟对象的另一个原因是为了更好地了解您如何在代码中使用它们的真实对应对象。 Python模拟对象包含您可以检查的有关其用法的数据,例如:
Understanding what a mock object does is the first step to learning how to use one.
了解模拟对象的功能是学习如何使用模拟对象的第一步。
Now, you’ll see how to use Python mock objects.
现在,您将看到如何使用Python模拟对象。
The Python mock object library is unittest.mock
. It provides an easy way to introduce mocks into your tests.
Python 模拟对象库是unittest.mock
。 它提供了一种将模拟引入测试的简便方法。
Note: The standard library includes unittest.mock
in Python 3.3 and later. If you’re using an older version of Python, you’ll need to install the official backport of the library. To do so, install mock
from PyPI:
注意:标准库在Python 3.3及更高版本中包含unittest.mock
。 如果您使用的是旧版本的Python,则需要安装该库的官方反向端口。 为此,请从PyPI安装mock
:
$ pip install mock
$ pip install mock
unittest.mock
provides a class called Mock
which you will use to imitate real objects in your codebase. Mock
offers incredible flexibility and insightful data. This, along with its subclasses, will meet most Python mocking needs that you will face in your tests.
unittest.mock
提供了一个名为Mock
的类,您可以使用该类来模仿代码库中的真实对象。 Mock
提供了令人难以置信的灵活性和洞察力的数据。 它及其子类将满足您在测试中将面临的大多数Python模拟需求。
The library also provides a function, called patch()
, which replaces the real objects in your code with Mock
instances. You can use patch()
as either a decorator or a context manager, giving you control over the scope in which the object will be mocked. Once the designated scope exits, patch()
will clean up your code by replacing the mocked objects with their original counterparts.
该库还提供了一个名为patch()
的函数,该函数用Mock
实例替换代码中的实际对象。 您可以将patch()
用作装饰器或上下文管理器,以控制对象的模拟范围。 一旦指定范围退出, patch()
将通过将模拟对象替换为其原始对应对象来清理代码。
Finally, unittest.mock
provides solutions for some of the issues inherent in mocking objects.
最后, unittest.mock
提供了模拟对象固有的一些问题的解决方案。
Now, you have a better understanding of what mocking is and the library you’ll be using to do it. Let’s dive in and explore what features and functionalities unittest.mock
offers.
现在,您对模拟是什么以及将用于执行模拟的库有了更好的了解。 让我们unittest.mock
提供的功能部件和功能。
Mock
对象 (The Mock
Object)unittest.mock
offers a base class for mocking objects called Mock
. The use cases for Mock
are practically limitless because Mock
is so flexible.
unittest.mock
提供了一个Mock
对象的基类Mock
。 Mock
的用例实际上是无限的,因为Mock
非常灵活。
Begin by instantiating a new Mock
instance:
首先实例化一个新的Mock
实例:
>>> from unittest.mock import Mock
>>> mock = Mock ()
>>> mock
<Mock id='4561344720'>
Now, you are able to substitute an object in your code with your new Mock
. You can do this by passing it as an argument to a function or by redefining another object:
现在,您可以用新的Mock
替换代码中的对象。 您可以通过将其作为参数传递给函数或重新定义另一个对象来实现:
When you substitute an object in your code, the Mock
must look like the real object it is replacing. Otherwise, your code will not be able to use the Mock
in place of the original object.
当您在代码中替换对象时, Mock
必须看起来像它要替换的真实对象。 否则,您的代码将无法使用Mock
代替原始对象。
For example, if you are mocking the json
library and your program calls dumps()
, then your Python mock object must also contain dumps()
.
例如,如果您正在模拟json
库,并且您的程序调用了dumps()
,那么您的Python模拟对象还必须包含dumps()
。
Next, you’ll see how Mock
deals with this challenge.
接下来,您将看到Mock
如何应对这一挑战。
A Mock
must simulate any object that it replaces. To achieve such flexibility, it creates its attributes when you access them:
Mock
必须模拟它替换的任何对象。 为了实现这种灵活性,它会在您访问属性时创建其属性 :
>>> mock.some_attribute
<Mock name='mock.some_attribute' id='4394778696'>
>>> mock.do_something()
<Mock name='mock.do_something()' id='4394778920'>
Since Mock
can create arbitrary attributes on the fly, it is suitable to replace any object.
由于Mock
可以动态创建任意属性,因此适合替换任何对象。
Using an example from earlier, if you’re mocking the json
library and you call dumps()
, the Python mock object will create the method so that its interface can match the library’s interface:
使用前面的示例,如果您要模拟json
库并调用dumps()
,则Python模拟对象将创建该方法,以便其接口可以匹配库的接口:
>>> json = Mock()
>>> json.dumps()
<Mock name='mock.dumps()' id='4392249776'>
Notice two key characteristics of this mocked version of dumps()
:
注意此模拟版本的dumps()
两个关键特征:
Unlike the real dumps()
, this mocked method requires no arguments. In fact, it will accept any arguments that you pass to it.
The return value of dumps()
is also a Mock
. The capability of Mock
to recursively define other mocks allows for you to use mocks in complex situations:
与实际的dumps()
,此模拟方法不需要参数。 实际上,它将接受您传递给它的所有参数。
dumps()
的返回值也是Mock
。 Mock
递归定义其他Mock
的功能使您可以在复杂的情况下使用Mock
:
>>> json = Mock()
>>> json.loads('{"k": "v"}').get('k')
<Mock name='mock.loads().get()' id='4379599424'>
Because the return value of each mocked method is also a Mock
, you can use your mocks in a multitude of ways.
因为每个Mock
方法的返回值也是Mock
,所以您可以通过多种方式使用Mock
。
Mocks are flexible, but they’re also informative. Next, you’ll learn how you can use mocks to understand your code better.
嘲弄很灵活,但也很有用。 接下来,您将学习如何使用模拟来更好地理解代码。
Mock
instances store data on how you used them. For instance, you can see if you called a method, how you called the method, and so on. There are two main ways to use this information.
Mock
实例存储有关如何使用它们的数据。 例如,您可以查看是否调用了方法,如何调用该方法等等。 使用此信息有两种主要方法。
First, you can assert that your program used an object as you expected:
首先,您可以断言您的程序按预期使用了一个对象:
>>> from unittest.mock import Mock
>>> # Create a mock object
... json = Mock()
>>> json.loads('{"key": "value"}')
<Mock name='mock.loads()' id='4550144184'>
>>> # You know that you called loads() so you can
>>> # make assertions to test that expectation
... json.loads.assert_called()
>>> json.loads.assert_called_once()
>>> json.loads.assert_called_with('{"key": "value"}')
>>> json.loads.assert_called_once_with('{"key": "value"}')
>>> json.loads('{"key": "value"}')
<Mock name='mock.loads()' id='4550144184'>
>>> # If an assertion fails, the mock will raise an AssertionError
... json.loads.assert_called_once()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 795, in assert_called_once
raise AssertionError(msg)
AssertionError: Expected 'loads' to have been called once. Called 2 times.
>>> json.loads.assert_called_once_with('{"key": "value"}')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 824, in assert_called_once_with
raise AssertionError(msg)
AssertionError: Expected 'loads' to be called once. Called 2 times.
>>> json.loads.assert_not_called()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 777, in assert_not_called
raise AssertionError(msg)
AssertionError: Expected 'loads' to not have been called. Called 2 times.
.assert_called()
ensures you called the mocked method while .assert_called_once()
checks that you called the method exactly one time.
.assert_called()
确保您调用了.assert_called_once()
方法,而.assert_called_once()
检查您是否恰好一次调用了该方法。
Both assertion functions have variants that let you inspect the arguments passed to the mocked method:
这两个断言函数都有变体,可让您检查传递给模拟方法的参数:
.assert_called_with(*args, **kwargs)
.assert_called_once_with(*args, **kwargs)
.assert_called_with(*args, **kwargs)
.assert_called_once_with(*args, **kwargs)
To pass these assertions, you must call the mocked method with the same arguments that you pass to the actual method:
要传递这些断言,必须使用传递给实际方法的相同参数调用模拟方法:
>>> json = Mock()
>>> json.loads(s='{"key": "value"}')
>>> json.loads.assert_called_with('{"key": "value"}')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 814, in assert_called_with
raise AssertionError(_error_message()) from cause
AssertionError: Expected call: loads('{"key": "value"}')
Actual call: loads(s='{"key": "value"}')
>>> json.loads.assert_called_with(s='{"key": "value"}')
json.loads.assert_called_with('{"key": "value"}')
raised an AssertionError
because it expected you to call loads()
with a positional argument, but you actually called it with a keyword argument. json.loads.assert_called_with(s='{"key": "value"}')
gets this assertion correct.
json.loads.assert_called_with('{"key": "value"}')
引发了AssertionError
因为它希望您使用位置参数来调用loads()
,但实际上是使用关键字参数来调用它。 json.loads.assert_called_with(s='{"key": "value"}')
可以正确获取此断言。
Second, you can view special attributes to understand how your application used an object:
其次,您可以查看特殊属性以了解您的应用程序如何使用对象:
>>> from unittest.mock import Mock
>>> # Create a mock object
... json = Mock()
>>> json.loads('{"key": "value"}')
<Mock name='mock.loads()' id='4391026640'>
>>> # Number of times you called loads():
... json.loads.call_count
1
>>> # The last loads() call:
... json.loads.call_args
call('{"key": "value"}')
>>> # List of loads() calls:
... json.loads.call_args_list
[call('{"key": "value"}')]
>>> # List of calls to json's methods (recursively):
... json.method_calls
[call.loads('{"key": "value"}')]
You can write tests using these attributes to make sure that your objects behave as you intended.
您可以使用这些属性编写测试,以确保您的对象按预期运行。
Now, you can create mocks and inspect their usage data. Next, you’ll see how to customize mocked methods so that they become more useful in your testing environment.
现在,您可以创建模拟并检查其使用数据。 接下来,您将看到如何自定义模拟方法,以使它们在您的测试环境中变得更加有用。
One reason to use mocks is to control your code’s behavior during tests. One way to do this is to specify a function’s return value. Let’s use an example to see how this works.
使用模拟的原因之一是在测试期间控制代码的行为。 一种实现方法是指定函数的返回值 。 我们来看一个例子。
First, create a file called my_calendar.py
. Add is_weekday()
, a function that uses Python’s datetime
library to determine whether or not today is a week day. Finally, write a test that asserts that the function works as expected:
首先,创建一个名为my_calendar.py
的文件。 添加is_weekday()
,该函数使用Python的datetime
库确定今天是否是工作日。 最后,编写一个断言该函数按预期工作的测试:
from from datetime datetime import import datetime
datetime
def def is_weekdayis_weekday ():
():
today today = = datetimedatetime .. todaytoday ()
()
# Python's datetime library treats Monday as 0 and Sunday as 6
# Python's datetime library treats Monday as 0 and Sunday as 6
return return (( 0 0 <= <= todaytoday .. weekdayweekday () () < < 55 )
)
# Test if today is a weekday
# Test if today is a weekday
assert assert is_weekdayis_weekday ()
()
Since you’re testing if today is a weekday, the result depends on the day you run your test:
由于您要测试今天是否是工作日,因此结果取决于您进行测试的日期:
If this command produces no output, the assertion was successful. Unfortunately, if you run the command on a weekend, you’ll get an AssertionError
:
如果此命令没有输出,则断言成功。 不幸的是,如果您在周末运行该命令,则会收到AssertionError
:
$ python my_calendar.py
$ python my_calendar.py
Traceback (most recent call last):
Traceback (most recent call last):
File "test.py", line 9, in <module>
File "test.py", line 9, in <module>
assert is_weekday()
assert is_weekday()
AssertionError
AssertionError
When writing tests, it is important to ensure that the results are predictable. You can use Mock
to eliminate uncertainty from your code during testing. In this case, you can mock datetime
and set the .return_value
for .today()
to a day that you choose:
在编写测试时,重要的是要确保结果是可预测的。 您可以在测试过程中使用Mock
消除代码中的不确定性。 在这种情况下,您可以模拟datetime
并将.today()
的.return_value
设置为您选择的一天:
In the example, .today()
is a mocked method. You’ve removed the inconsistency by assigning a specific day to the mock’s .return_value
. That way, when you call .today()
, it returns the datetime
that you specified.
在示例中, .today()
是一种.today()
方法。 通过为模拟的.return_value
分配特定的日期,您已经消除了不一致的.return_value
。 这样,当您调用.today()
,它将返回您指定的datetime
。
In the first test, you ensure tuesday
is a weekday. In the second test, you verify that saturday
is not a weekday. Now, it doesn’t matter what day you run your tests on because you’ve mocked datetime
and have control over the object’s behavior.
在第一个测试中,您确保tuesday
是工作日。 在第二个测试中,您验证saturday
不是工作日。 现在,在哪一天进行测试都没有关系,因为您已经模拟了datetime
并可以控制对象的行为。
Further Reading: Though mocking datetime
like this is a good practice example for using Mock
, a fantastic library already exists for mocking datetime
called freezegun
.
进一步阅读:尽管像这样的Mock
datetime
是使用Mock
一个好习惯示例,但已经存在一个用于Mock
datetime
的奇妙库,称为freezegun
。
When building your tests, you will likely come across cases where mocking a function’s return value will not be enough. This is because functions are often more complicated than a simple one-way flow of logic.
在构建测试时,可能会遇到仅模仿函数的返回值还不够的情况。 这是因为功能通常比简单的单向逻辑流程更复杂。
Sometimes, you’ll want to make functions return different values when you call them more than once or even raise exceptions. You can do this using .side_effect
.
有时,您将希望使函数在多次调用它们时返回不同的值,甚至引发异常。 您可以使用.side_effect
进行此操作。
You can control your code’s behavior by specifying a mocked function’s side effects. A .side_effect
defines what happens when you call the mocked function.
您可以通过指定模拟函数的副作用来控制代码的行为。 .side_effect
定义调用.side_effect
函数时发生的情况。
To test how this works, add a new function to my_calendar.py
:
要测试其工作原理,请向my_calendar.py
添加一个新函数:
import import requests
requests
def def get_holidaysget_holidays ():
():
r r = = requestsrequests .. getget (( 'http://localhost/api/holidays''http://localhost/api/holidays' )
)
if if rr .. status_code status_code == == 200200 :
:
return return rr .. jsonjson ()
()
return return None
None
get_holidays()
makes a request to the localhost
server for a set of holidays. If the server responds successfully, get_holidays()
will return a dictionary. Otherwise, the method will return None
.
get_holidays()
向localhost
服务器请求一组假期。 如果服务器成功响应,则get_holidays()
将返回字典。 否则,该方法将返回None
。
You can test how get_holidays()
will respond to a connection timeout by setting requests.get.side_effect
.
您可以测试如何get_holidays()
将一个连接超时设置响应requests.get.side_effect
。
For this example, you’ll only see the relevant code from my_calendar.py
. You’ll build a test case using Python’s unittest
library:
对于此示例,您只会从my_calendar.py
看到相关代码。 您将使用Python的unittest
库构建一个测试用例:
You use .assertRaises()
to verify that get_holidays()
raises an exception given the new side effect of get()
.
您可以使用.assertRaises()
来验证get_holidays()
在给定get()
的新副作用的情况下引发了异常。
Run this test to see the result of your test:
运行此测试以查看测试结果:
$ python my_calendar.py
$ python my_calendar.py
.
.
-------------------------------------------------------
-------------------------------------------------------
Ran 1 test in 0.000s
Ran 1 test in 0.000s
OK
OK
If you want to be a little more dynamic, you can set .side_effect
to a function that Mock
will invoke when you call your mocked method. The mock shares the arguments and return value of the .side_effect
function:
如果您想提高动态性,可以将.side_effect
设置为在调用Mock
方法时Mock
将调用的函数。 该模拟共享.side_effect
函数的参数和返回值:
First, you created .log_request()
, which takes a URL, logs some output using print()
, then returns a Mock
response. Next, you set the .side_effect
of get()
to .log_request()
, which you’ll use when you call get_holidays()
. When you run your test, you’ll see that get()
forwards its arguments to .log_request()
then accepts the return value and returns it as well:
首先,您创建了.log_request()
,它使用一个URL,使用print()
记录一些输出,然后返回一个Mock
响应。 接下来,将get()
的.side_effect
设置为.log_request()
,在调用get_holidays()
时将使用它。 运行测试时,您会看到get()
将其参数转发到.log_request()
然后接受返回值并返回它:
$ python my_calendar.py
$ python my_calendar.py
Making a request to http://localhost/api/holidays.
Making a request to http://localhost/api/holidays.
Request received!
Request received!
.
.
-------------------------------------------------------
-------------------------------------------------------
Ran 1 test in 0.000s
Ran 1 test in 0.000s
OK
OK
Great! The print()
statements logged the correct values. Also, get_holidays()
returned the holidays dictionary.
大! print()
语句记录了正确的值。 另外, get_holidays()
返回了假日字典。
.side_effect
can also be an iterable. The iterable must consist of return values, exceptions, or a mixture of both. The iterable will produce its next value every time you call your mocked method. For example, you can test that a retry after a Timeout
returns a successful response:
.side_effect
也可以是可迭代的。 迭代器必须包含返回值,异常或两者的混合。 每次调用模拟方法时,迭代器都会产生其下一个值。 例如,您可以测试Timeout
后重试是否返回成功响应:
The first time you call get_holidays()
, get()
raises a Timeout
. The second time, the method returns a valid holidays dictionary. These side effects match the order they appear in the list passed to .side_effect
.
首次调用get_holidays()
, get()
引发Timeout
。 第二次,该方法返回有效的假期字典。 这些副作用与传递给.side_effect
的列表中出现的顺序匹配。
You can set .return_value
and .side_effect
on a Mock
directly. However, because a Python mock object needs to be flexible in creating its attributes, there is a better way to configure these and other settings.
您可以直接在Mock
上设置.return_value
和.side_effect
。 但是,由于Python模拟对象在创建其属性时需要灵活,因此有一种更好的方法来配置这些设置和其他设置。
You can configure a Mock
to set up some of the object’s behaviors. Some configurable members include .side_effect
, .return_value
, and .name
. You configure a Mock
when you create one or when you use .configure_mock()
.
您可以配置一个Mock
来设置对象的某些行为。 一些可配置的成员包括.side_effect
, .return_value
和.name
。 您可以在创建 Mock
或使用.configure_mock()
时配置Mock
。
You can configure a Mock
by specifying certain attributes when you initialize an object:
您可以在初始化对象时通过指定某些属性来配置Mock
:
>>> mock = Mock(side_effect=Exception)
>>> mock()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 939, in __call__
return _mock_self._mock_call(*args, **kwargs)
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 995, in _mock_call
raise effect
Exception
>>> mock = Mock(name='Real Python Mock')
>>> mock
<Mock name='Real Python Mock' id='4434041432'>
>>> mock = Mock(return_value=True)
>>> mock()
True
While .side_effect
and .return_value
can be set on the Mock
instance, itself, other attributes like .name
can only be set through .__init__()
or .configure_mock()
. If you try to set the .name
of the Mock
on the instance, you will get a different result:
尽管可以在Mock
实例上设置.side_effect
和.return_value
,但其他属性(如.name
只能通过.__init__()
或.configure_mock()
。 如果尝试在实例上设置Mock
的.name
,则会得到不同的结果:
>>> mock = Mock(name='Real Python Mock')
>>> mock.name
<Mock name='Real Python Mock.name' id='4434041544'>
>>> mock = Mock()
>>> mock.name = 'Real Python Mock'
>>> mock.name
'Real Python Mock'
.name
is a common attribute for objects to use. So, Mock
doesn’t let you set that value on the instance in the same way you can with .return_value
or .side_effect
. If you access mock.name
you will create a .name
attribute instead of configuring your mock.
.name
是要使用的对象的常用属性。 因此, Mock
不允许您以与.return_value
或.side_effect
相同的方式在实例上设置该值。 如果访问嘲笑。 mock.name
,则将创建.name
属性,而不是配置嘲笑。
You can configure an existing Mock
using .configure_mock()
:
您可以使用.configure_mock()
配置现有的Mock
:
>>> mock = Mock()
>>> mock.configure_mock(return_value=True)
>>> mock()
True
By unpacking a dictionary into either .configure_mock()
or Mock.__init__()
, you can even configure your Python mock object’s attributes. Using Mock
configurations, you could simplify a previous example:
通过将字典解.configure_mock()
到.configure_mock()
或Mock.__init__()
,您甚至可以配置Python模拟对象的属性。 使用Mock
配置,可以简化前面的示例:
# Verbose, old Mock
# Verbose, old Mock
response_mock response_mock = = MockMock ()
()
response_mockresponse_mock .. jsonjson .. return_value return_value = = {
{
'12/25''12/25' : : 'Christmas''Christmas' ,
,
'7/4''7/4' : : 'Independence Day''Independence Day' ,
,
}
}
# Shiny, new .configure_mock()
# Shiny, new .configure_mock()
holidays holidays = = {{ '12/25''12/25' : : 'Christmas''Christmas' , , '7/4''7/4' : : 'Independence Day''Independence Day' }
}
response_mock response_mock = = MockMock (( **** {{ 'json.return_value''json.return_value' : : holidaysholidays })
})
Now, you can create and configure Python mock objects. You can also use mocks to control the behavior of your application. So far, you’ve used mocks as arguments to functions or patching objects in the same module as your tests.
现在,您可以创建和配置Python模拟对象。 您还可以使用模拟来控制应用程序的行为。 到目前为止,您已经使用了模拟作为函数的参数或在与测试相同的模块中修补对象。
Next, you’ll learn how to substitute your mocks for real objects in other modules.
接下来,您将学习如何在其他模块中用模拟代替真实对象。
patch()
(patch()
)unittest.mock
provides a powerful mechanism for mocking objects, called patch()
, which looks up an object in a given module and replaces that object with a Mock
.
unittest.mock
提供了一种用于Mock
对象的强大机制,称为patch()
,该机制可以在给定模块中查找对象并将其替换为Mock
。
Usually, you use patch()
as a decorator or a context manager to provide a scope in which you will mock the target object.
通常,您将patch()
用作装饰器或上下文管理器,以提供在其中模拟目标对象的范围。
patch()
作为装饰器 (patch()
as a Decorator)If you want to mock an object for the duration of your entire test function, you can use patch()
as a function decorator.
如果要在整个测试功能期间模拟对象,可以将patch()
用作函数装饰器 。
To see how this works, reorganize your my_calendar.py
file by putting the logic and tests into separate files:
要查看其工作原理,请通过将逻辑和测试放入单独的文件中来重新组织my_calendar.py
文件:
These functions are now in their own file, separate from their tests. Next, you’ll re-create your tests in a file called tests.py
.
这些功能现在与测试分开存放在自己的文件中。 接下来,您将在一个名为tests.py
的文件中重新创建测试。
Up to this point, you’ve monkey patched objects in the file in which they exist. Monkey patching is the replacement of one object with another at runtime. Now, you’ll use patch()
to replace your objects in my_calendar.py
:
到目前为止,您已经在对象存在的文件中添加了猴子补丁。 猴子修补是在运行时将一个对象替换为另一个对象。 现在,您将使用patch()
替换my_calendar.py
的对象:
import import unittest
unittest
from from my_calendar my_calendar import import get_holidays
get_holidays
from from requests.exceptions requests.exceptions import import Timeout
Timeout
from from unittest.mock unittest.mock import import patch
patch
class class TestCalendarTestCalendar (( unittestunittest .. TestCaseTestCase ):
):
@patch@patch (( 'my_calendar.requests''my_calendar.requests' )
)
def def test_get_holidays_timeouttest_get_holidays_timeout (( selfself , , mock_requestsmock_requests ):
):
mock_requestsmock_requests .. getget .. side_effect side_effect = = Timeout
Timeout
with with selfself .. assertRaisesassertRaises (( TimeoutTimeout ):
):
get_holidaysget_holidays ()
()
mock_requestsmock_requests .. getget .. assert_called_onceassert_called_once ()
()
if if __name__ __name__ == == '__main__''__main__' :
:
unittestunittest .. mainmain ()
()
Originally, you created a Mock
and patched requests
in the local scope. Now, you need to access the requests
library in my_calendar.py
from tests.py
.
最初,您创建了一个Mock
并在本地范围内修补了requests
。 现在,您需要从tests.py
访问my_calendar.py
的requests
库。
For this case, you used patch()
as a decorator and passed the target object’s path. The target path was 'my_calendar.requests'
which consists of the module name and the object.
对于这种情况,您使用patch()
作为装饰器,并传递了目标对象的路径。 目标路径是'my_calendar.requests'
,它由模块名称和对象组成。
You also defined a new parameter for the test function. patch()
uses this parameter to pass the mocked object into your test. From there, you can modify the mock or make assertions as necessary.
您还为测试功能定义了一个新参数。 patch()
使用此参数将模拟对象传递到测试中。 从那里,您可以根据需要修改模拟或进行断言。
You can execute this test module to ensure it’s working as expected:
您可以执行此测试模块以确保其按预期工作:
Technical Detail: patch()
returns an instance of MagicMock
, which is a Mock
subclass. MagicMock
is useful because it implements most magic methods for you, such as .__len__()
, .__str__()
, and .__iter__()
, with reasonable defaults.
技术细节: patch()
返回MagicMock
的实例,它是Mock
子类。 MagicMock
很有用,因为它为您实现了大多数魔术方法 ,例如.__len__()
, .__str__()
和.__iter__()
,并且具有合理的默认值。
Using patch()
as a decorator worked well in this example. In some cases, it is more readable, more effective, or easier to use patch()
as a context manager.
在此示例中,使用patch()
作为装饰器效果很好。 在某些情况下,使用patch()
作为上下文管理器更易读,更有效或更容易。
patch()
作为上下文管理器 (patch()
as a Context Manager)Sometimes, you’ll want to use patch()
as a context manager rather than a decorator. Some reasons why you might prefer a context manager include the following:
有时,您需要使用patch()
作为上下文管理器而不是装饰器。 您可能更喜欢上下文管理器的一些原因包括:
To use patch()
as a context manager, you use Python’s with
statement:
要将patch()
用作上下文管理器,请使用Python的with
语句:
import import unittest
unittest
from from my_calendar my_calendar import import get_holidays
get_holidays
from from requests.exceptions requests.exceptions import import Timeout
Timeout
from from unittest.mock unittest.mock import import patch
patch
class class TestCalendarTestCalendar (( unittestunittest .. TestCaseTestCase ):
):
def def test_get_holidays_timeouttest_get_holidays_timeout (( selfself ):
):
with with patchpatch (( 'my_calendar.requests''my_calendar.requests' ) ) as as mock_requestsmock_requests :
:
mock_requestsmock_requests .. getget .. side_effect side_effect = = Timeout
Timeout
with with selfself .. assertRaisesassertRaises (( TimeoutTimeout ):
):
get_holidaysget_holidays ()
()
mock_requestsmock_requests .. getget .. assert_called_onceassert_called_once ()
()
if if __name__ __name__ == == '__main__''__main__' :
:
unittestunittest .. mainmain ()
()
When the test exits the with
statement, patch()
replaces the mocked object with the original.
当测试退出with
语句时, patch()
会将模拟对象替换为原始对象。
Until now, you’ve mocked complete objects, but sometimes you’ll only want to mock a part of an object.
到目前为止,您已经模拟了完整的对象,但有时您只想模拟对象的一部分。
Let’s say you only want to mock one method of an object instead of the entire object. You can do so by using patch.object()
.
假设您只想模拟对象的一种方法,而不是整个对象。 您可以使用patch.object()
。
For example, .test_get_holidays_timeout()
really only needs to mock requests.get()
and set its .side_effect
to Timeout
:
例如, .test_get_holidays_timeout()
实际上只需要模拟.side_effect
requests.get()
并将其.side_effect
设置为Timeout
:
In this example, you’ve mocked only get()
rather than all of requests
. Every other attribute remains the same.
在此示例中,您仅嘲笑了get()
而不是所有的requests
。 其他所有属性保持不变。
object()
takes the same configuration parameters that patch()
does. But instead of passing the target’s path, you provide the target object, itself, as the first parameter. The second parameter is the attribute of the target object that you are trying to mock. You can also use object()
as a context manager like patch()
.
object()
具有与patch()
相同的配置参数。 但是,您无需传递目标的路径,而是将目标对象本身作为第一个参数。 第二个参数是您要模拟的目标对象的属性。 您还可以将object()
用作上下文管理器,例如patch()
。
Further Reading: Besides objects and attributes, you can also patch()
dictionaries with patch.dict()
.
进一步阅读:除了对象和属性,您还可以使用patch.dict()
来patch()
字典。
Learning how to use patch()
is critical to mocking objects in other modules. However, sometimes it’s not obvious what the target object’s path is.
学习如何使用patch()
对于模拟其他模块中的对象至关重要。 但是,有时不清楚目标对象的路径是什么。
Knowing where to tell patch()
to look for the object you want mocked is important because if you choose the wrong target location, the result of patch()
could be something you didn’t expect.
知道在哪里告诉patch()
查找要模拟的对象很重要,因为如果您选择了错误的目标位置,则patch()
的结果可能是您没有想到的。
Let’s say you are mocking is_weekday()
in my_calendar.py
using patch()
:
比方说,你在嘲讽is_weekday()
在my_calendar.py
使用patch()
>>> import my_calendar
>>> from unittest.mock import patch
>>> with patch('my_calendar.is_weekday'):
... my_calendar.is_weekday()
...
<MagicMock name='is_weekday()' id='4336501256'>
First, you import my_calendar.py
. Then you patch is_weekday()
, replacing it with a Mock
. Great! This is working as expected.
首先,导入my_calendar.py
。 然后修补is_weekday()
,将其替换为Mock
。 大! 这正在按预期方式工作。
Now, let’s change this example slightly and import the function directly:
现在,让我们稍微更改一下此示例,然后直接导入函数:
>>> from my_calendar import is_weekday
>>> from unittest.mock import patch
>>> with patch('my_calendar.is_weekday'):
... is_weekday()
...
False
Note: Depending on what day you are reading this tutorial, your console output may read True
or False
. The important thing is that the output is not a Mock
like before.
注意:根据您正在阅读本教程的哪一天,您的控制台输出可能显示True
或False
。 重要的是输出不是以前的Mock
。
Notice that even though the target location you passed to patch()
did not change, the result of calling is_weekday()
is different. The difference is due to the change in how you imported the function.
请注意,即使您传递给patch()
的目标位置没有改变,调用is_weekday()
的结果也有所不同。 差异是由于导入功能的方式发生了变化。
from my_calendar import is_weekday
binds the real function to the local scope. So, even though you patch()
the function later, you ignore the mock because you already have a local reference to the un-mocked function.
from my_calendar import is_weekday
实函数绑定到本地范围。 因此,即使稍后再对函数进行patch()
,您也可以忽略该模拟,因为您已经具有对未模拟函数的本地引用。
A good rule of thumb is to patch()
the object where it is looked up.
一个好的经验法则是在要查找对象的位置patch()
。
In the first example, mocking 'my_calendar.is_weekday()'
works because you look up the function in the my_calendar
module. In the second example, you have a local reference to is_weekday()
. Since you use the function found in the local scope, you should mock the local function:
在第一个示例中, 'my_calendar.is_weekday()'
工作原理是因为您在my_calendar
模块中查找了该函数。 在第二个示例中,您具有对is_weekday()
的本地引用。 由于您使用的是在本地作用域中找到的函数,因此应模拟本地函数:
>>> from unittest.mock import patch
>>> from my_calendar import is_weekday
>>> with patch('__main__.is_weekday'):
... is_weekday()
...
<MagicMock name='is_weekday()' id='4502362992'>
Now, you have a firm grasp on the power of patch()
. You’ve seen how to patch()
objects and attributes as well as where to patch them.
现在,您已经掌握了patch()
。 您已经了解了如何patch()
对象和属性以及在何处对其进行修补。
Next, you’ll see some common problems inherent in object mocking and the solutions that unittest.mock
provides.
接下来,您将看到对象模拟和unittest.mock
提供的解决方案固有的一些常见问题。
Mocking objects can introduce several problems into your tests. Some problems are inherent in mocking while others are specific to unittest.mock
. Keep in mind that there are other issues with mocking that are not mentioned in this tutorial.
模拟对象可能会在测试中引入一些问题。 一些问题是模拟固有的,而另一些问题是unittest.mock
特有的。 请记住,本教程中没有提到其他有关模拟的问题。
The ones covered here are similar to each other in that the problem they cause is fundamentally the same. In each case, the test assertions are irrelevant. Though the intention of each mock is valid, the mocks themselves are not.
此处介绍的问题彼此相似,因为它们引起的问题在本质上是相同的。 在每种情况下,测试断言都是不相关的。 尽管每个模拟的意图都是正确的,但模拟本身并非如此。
Classes and function definitions change all the time. When the interface of an object changes, any tests relying on a Mock
of that object may become irrelevant.
类和函数定义一直在变化。 当对象的界面更改时,任何依赖于该对象的Mock
的测试都可能变得无关紧要。
For example, you rename a method but forget that a test mocks that method and invokes .assert_not_called()
. After the change, .assert_not_called()
is still True
. The assertion is not useful, though, because the method no longer exists.
例如,您重命名方法,但是忘记了测试会.assert_not_called()
该方法并调用.assert_not_called()
。 更改后, .assert_not_called()
仍为True
。 但是断言没有用,因为该方法不再存在。
Irrelevant tests may not sound critical, but if they are your only tests and you assume that they work properly, the situation could be disastrous for your application.
不相关的测试听起来并不重要,但是如果它们只是您的唯一测试,并且您假设它们可以正常工作,那么这种情况可能会对您的应用程序造成灾难性的影响。
A problem specific to Mock
is that a misspelling can break a test. Recall that a Mock
creates its interface when you access its members. So, you will inadvertently create a new attribute if you misspell its name.
Mock
特有的问题是拼写错误可能会破坏测试。 回想一下,当您访问其成员时, Mock
会创建其接口。 因此,如果您拼写错误的名称,将会无意间创建一个新属性。
If you call .asert_called()
instead of .assert_called()
, your test will not raise an AssertionError
. This is because you’ve created a new method on the Python mock object named .asert_called()
instead of evaluating an actual assertion.
如果调用.asert_called()
而不是.assert_called()
,则测试不会引发AssertionError
。 这是因为您已经在名为.asert_called()
的Python模拟对象上创建了一个新方法,而不是评估实际的断言。
Technical Detail: Interestingly, assret
is a special misspelling of assert
. If you try to access an attribute that starts with assret
(or assert
), Mock
will automatically raise an AttributeError
.
技术细节:有趣的是, assret
是assert
的特殊拼写错误。 如果尝试访问以assret
(或assert
)开头的属性,则Mock
将自动引发AttributeError
。
These problems occur when you mock objects within your own codebase. A different problem arises when you mock objects interacting with external codebases.
当您在自己的代码库中模拟对象时,会发生这些问题。 模拟对象与外部代码库交互时,会出现另一个问题。
Imagine again that your code makes a request to an external API. In this case, the external dependency is the API which is susceptible to change without your consent.
再次想象一下,您的代码向外部API发出了请求。 在这种情况下,外部依赖项是未经您同意即可更改的API。
On one hand, unit tests test isolated components of code. So, mocking the code that makes the request helps you to test your isolated components under controlled conditions. However, it also presents a potential problem.
一方面,单元测试测试代码的隔离组件。 因此,模拟发出请求的代码可帮助您在受控条件下测试隔离的组件。 但是,这也存在潜在的问题。
If an external dependency changes its interface, your Python mock objects will become invalid. If this happens (and the interface change is a breaking one), your tests will pass because your mock objects have masked the change, but your production code will fail.
如果外部依赖项更改了其接口,则您的Python模拟对象将变为无效。 如果发生这种情况(并且接口更改是一项重大工作),则测试将通过,因为您的模拟对象掩盖了更改,但是生产代码将失败。
Unfortunately, this is not a problem that unittest.mock
provides a solution for. You must exercise judgment when mocking external dependencies.
不幸的是,这不是unittest.mock
提供解决方案的问题。 模拟外部依赖项时,必须行使判断力。
All three of these issues can cause test irrelevancy and potentially costly issues because they threaten the integrity of your mocks. unittest.mock
gives you some tools for dealing with these problems.
所有这三个问题都可能导致测试无关紧要,并可能导致代价高昂的问题,因为它们威胁到模拟的完整性。 unittest.mock
提供了一些用于解决这些问题的工具。
As mentioned before, if you change a class or function definition or you misspell a Python mock object’s attribute, you can cause problems with your tests.
如前所述,如果您更改类或函数定义或对Python模拟对象的属性拼写错误,则可能导致测试问题。
These problems occur because Mock
creates attributes and methods when you access them. The answer to these issues is to prevent Mock
from creating attributes that don’t conform to the object you’re trying to mock.
发生这些问题的原因是,当您访问Mock
时,它们会创建属性和方法。 这些问题的答案是防止Mock
创建与您要模拟的对象不符的属性。
When configuring a Mock
, you can pass an object specification to the spec
parameter. The spec
parameter accepts a list of names or another object and defines the mock’s interface. If you attempt to access an attribute that does not belong to the specification, Mock
will raise an AttributeError
:
配置Mock
,可以将对象规范传递给spec
参数。 spec
参数接受名称或其他对象的列表,并定义模拟的接口。 如果您尝试访问不属于规范的AttributeError
,则Mock
将引发AttributeError
:
>>> from unittest.mock import Mock
>>> calendar = Mock(spec=['is_weekday', 'get_holidays'])
>>> calendar.is_weekday()
<Mock name='mock.is_weekday()' id='4569015856'>
>>> calendar.create_event()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 582, in __getattr__
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'create_event'
Here, you’ve specified that calendar
has methods called .is_weekday()
and .get_holidays()
. When you access .is_weekday()
, it returns a Mock
. When you access .create_event()
, a method that does not match the specification, Mock
raises an AttributeError
.
在这里,您已指定calendar
具有名为.is_weekday()
和.get_holidays()
。 当您访问.is_weekday()
,它将返回Mock
。 当您访问不符合规范的方法.create_event()
, Mock
会引发AttributeError
。
Specifications work the same way if you configure the Mock
with an object:
如果为Mock
配置对象,规范的工作方式相同:
>>> import my_calendar
>>> from unittest.mock import Mock
>>> calendar = Mock(spec=my_calendar)
>>> calendar.is_weekday()
<Mock name='mock.is_weekday()' id='4569435216'>
>>> calendar.create_event()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 582, in __getattr__
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'create_event'
.is_weekday()
is available to calendar
because you configured calendar
to match the my_calendar
module’s interface.
.is_weekday()
可用于calendar
因为您配置了calendar
以匹配my_calendar
模块的界面。
Furthermore, unittest.mock
provides convenient methods of automatically specifying a Mock
instance’s interface.
此外, unittest.mock
提供了方便的方法来自动指定Mock
实例的接口。
One way to implement automatic specifications is create_autospec
:
实现自动规范的一种方法是create_autospec
:
>>> import my_calendar
>>> from unittest.mock import create_autospec
>>> calendar = create_autospec(my_calendar)
>>> calendar.is_weekday()
<MagicMock name='mock.is_weekday()' id='4579049424'>
>>> calendar.create_event()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 582, in __getattr__
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'create_event'
Like before, calendar
is a Mock
instance whose interface matches my_calendar
. If you’re using patch()
, you can send an argument to the autospec
parameter to achieve the same result:
像以前一样, calendar
是一个Mock
实例,其接口与my_calendar
匹配。 如果使用patch()
,则可以将参数发送给autospec
参数以实现相同的结果:
>>> import my_calendar
>>> from unittest.mock import patch
>>> with patch('__main__.my_calendar', autospec=True) as calendar:
... calendar.is_weekday()
... calendar.create_event()
...
<MagicMock name='my_calendar.is_weekday()' id='4579094312'>
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 582, in __getattr__
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'create_event'
You’ve learned so much about mocking objects using unittest.mock
!
您已经学到了很多有关使用unittest.mock
对象的知识!
Now, you’re able to:
现在,您可以:
Mock
to imitate objects in your testspatch()
objects throughout your codebaseMock
模仿测试中的对象 patch()
对象 You have built a foundation of understanding that will help you build better tests. You can use mocks to gain insights into your code that you would not have been able to get otherwise.
您已经建立了了解的基础,可以帮助您建立更好的测试。 您可以使用模拟来获得对代码的深入了解,否则您将无法获得这些见解。
I leave you with one final disclaimer. Beware of overusing mock objects!
最后一个免责声明。 当心过度使用模拟对象!
It’s easy to take advantage of the power of Python mock objects and mock so much that you actually decrease the value of your tests.
充分利用Python模拟对象的强大功能并进行大量模拟很容易使您实际上降低了测试的价值。
If you’re interested in learning more about unittest.mock
, I encourage you to read its excellent documentation.
如果您有兴趣了解有关unittest.mock
更多信息,建议您阅读其出色的文档 。
翻译自: https://www.pybloggers.com/2019/03/understanding-the-python-mock-object-library/
mock python