mock python_了解Python Mock对象库

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:

到本文结尾,您将能够:

  • Create Python mock objects using Mock
  • Assert you’re using objects as you intended
  • Inspect usage data stored on your Python mocks
  • Configure certain aspects of your Python mock objects
  • Substitute your mocks for real objects using patch()
  • Avoid common problems inherent in Python mocking
  • 使用Mock创建Python模拟对象
  • 断言您正在按预期使用对象
  • 检查存储在Python模拟中的使用情况数据
  • 配置Python模拟对象的某些方面
  • 使用patch()将模拟替换为真实对象
  • 避免Python建模固有的常见问题

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技能提升到新水平所需的路线图和心态。

什么是模拟? (What Is Mocking?)

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模拟对象包含您可以检查的有关其用法的数据,例如:

  • If you called a method
  • How you called the method
  • How often you called the method
  • 如果您调用方法
  • 您如何称呼该方法
  • 您调用该方法的频率

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模拟对象。

Python Mock库 (The Python Mock Library)

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对象的基类MockMock的用例实际上是无限的,因为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如何应对这一挑战。

惰性属性和方法 (Lazy Attributes and Methods)

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()两个关键特征:

  1. Unlike the real dumps(), this mocked method requires no arguments. In fact, it will accept any arguments that you pass to it.

  2. 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:

  1. 实际的dumps() ,此模拟方法不需要参数。 实际上,它将接受您传递给它的所有参数。

  2. dumps()的返回值也是MockMock递归定义其他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.

嘲弄很灵活,但也很有用。 接下来,您将学习如何使用模拟来更好地理解代码。

断言和检查 (Assertions and Inspection)

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.

现在,您可以创建模拟并检查其使用数据。 接下来,您将看到如何自定义模拟方法,以使它们在您的测试环境中变得更加有用。

管理模拟返回值 (Managing a Mock’s Return Value)

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进行此操作。

管理模拟的副作用 (Managing a Mock’s Side Effects)

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模拟对象在创建其属性时需要灵活,因此有一种更好的方法来配置这些设置和其他设置。

配置模拟 (Configuring Your Mock)

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.pyrequests库。

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()作为上下文管理器而不是装饰器。 您可能更喜欢上下文管理器的一些原因包括:

  • You only want to mock an object for a part of the test scope.
  • You are already using too many decorators or parameters, which hurts your test’s readability.
  • 您只想模拟一部分测试范围的对象。
  • 您已经使用了太多的修饰符或参数,这会损害测试的可读性。

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.

到目前为止,您已经模拟了完整的对象,但有时您只想模拟对象的一部分。

修补对象的属性 (Patching an Object’s Attributes)

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()对于模拟其他模块中的对象至关重要。 但是,有时不清楚目标对象的路径是什么。

在哪里修补 (Where to 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.

注意:根据您正在阅读本教程的哪一天,您的控制台输出可能显示TrueFalse 。 重要的是输出不是以前的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提供的解决方案固有的一些常见问题。

常见的模拟问题 (Common Mocking Problems)

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.

此处介绍的问题彼此相似,因为它们引起的问题在本质上是相同的。 在每种情况下,测试断言都是不相关的。 尽管每个模拟的意图都是正确的,但模拟本身并非如此。

对象接口和拼写错误的更改 (Changes to Object Interfaces and Misspellings)

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.

技术细节:有趣的是, assretassert的特殊拼写错误。 如果尝试访问以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.

当您在自己的代码库中模拟对象时,会发生这些问题。 模拟对象与外部代码库交互时,会出现另一个问题。

外部依赖关系的变化 (Changes to External Dependencies)

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提供了一些用于解决这些问题的工具。

避免使用规格的常见问题 (Avoiding Common Problems Using Specifications)

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'

>>>

结论 (Conclusion)

You’ve learned so much about mocking objects using unittest.mock!

您已经学到了很多有关使用unittest.mock对象的知识!

Now, you’re able to:

现在,您可以:

  • Use Mock to imitate objects in your tests
  • Check usage data to understand how you use your objects
  • Customize your mock objects’ return values and side effects
  • patch() objects throughout your codebase
  • See and avoid problems with using Python mock objects
  • 使用Mock模仿测试中的对象
  • 检查使用情况数据以了解如何使用对象
  • 自定义模拟对象的返回值和副作用
  • 整个代码库中的patch()对象
  • 查看并避免使用Python模拟对象的问题

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值