您听说过 Python mock 和 patch 是改进单元测试的一种方法吗?您将在本教程中学习如何使用它们。
单元测试是软件开发的关键,因为它们能确保代码按计划运行。
Python 有许多强大的工具,可以通过创建 mock 在受控环境中运行单元测试。Mock 类是 unittest.mock 库的一部分,它允许创建 mock 对象。同一库中的 patch 函数允许用模拟对象替换真实对象。
让我们来了解一下 Python 模拟!
在 Python 中使用哪个模块进行模拟?
模拟是单元测试中使用的一种技术,它可以用模拟对象替换代码中不容易测试的部分,模拟对象复制了真实对象的行为。
Python unittest.mock 库为在单元测试中构建和操作模拟对象提供了一个框架。
模拟对象有助于创建受控的测试环境。它们模拟真实对象的行为,但其输入和输出是可以控制的。这样,您就可以隔离和测试那些由于外部依赖性而很难测试的代码。
通过导入 unittest 模块,您可以使用 mock 类和 patch 函数
import unittest
如何使用 Python Mock 类创建模拟对象?
要创建 mock 对象,可以使用 unittest 模块中的 Mock 类。
>>> from unittest.mock import Mock
>>> Mock
<class 'unittest.mock.Mock'>
我们从 unittest.mock 中导入了 Mock 类。现在我们可以创建一个 Mock 对象。
>>> mock = Mock()
>>> mock
<Mock id='140397919063152'>
Mock 的目的是替换真实的 Python 对象,但要做到这一点,Mock 必须提供与真实对象相同的方法和属性。
那么,Python 模拟是如何处理这个问题的呢?
让我们试着获取我们创建的 mock 的属性值或调用 mock 的方法:
>>> mock.length
<Mock name='mock.length' id='140397919064016'>
>>> mock.calculate_length(3, 6)
<Mock name='mock.calculate_length()' id='140398188475584'>
你可以看到,这两种方法都起作用了。这是因为 Mock 类动态创建了属性和方法。这使得模拟对象可以替代任何真实对象。
下面是我们的模拟对象的所有属性:
>>> mock.__dict__
{
'_mock_return_value': sentinel.DEFAULT, '_mock_parent': None, '_mock_name': None, '_mock_new_name': '', '_mock_new_parent': None, '_mock_sealed': False, '_spec_class': None, '_spec_set': None, '_spec_signature': None, '_mock_methods': None, '_spec_asyncs': [], '_mock_children': {
'length': <Mock name='mock.length' id='140397919064016'>, 'calculate_length': <Mock name='mock.calculate_length' id='140397919105184'>}, '_mock_wraps': None, '_mock_delegate': None, '_mock_called': False, '_mock_call_args': None, '_mock_call_count': 0, '_mock_call_args_list': [], '_mock_mock_calls': [call.calculate_length(3, 6)], 'method_calls': [call.calculate_length(3, 6)], '_mock_unsafe': False, '_mock_side_effect': None}
其中最有趣的是"_mock_children "属性,它包含在上述代码中动态创建的属性和方法。
在访问属性或调用方法时,Mock 对象会自动创建子对象。
断言对 Python 模拟的调用
在上一节中,我们调用了 mock 对象的一个方法。现在我们可以断言模拟对象上的特定方法已被调用。
下面是成功的断言。这些断言测试了 mock 的 calculate_length() 方法是否被调用:
- 已被调用。
- 已调用一次。
- 已调用参数 3 和 6。
- 已调用一次,参数为 3 和 6。
如果 mock 方法的断言失败,mock 会引发 AssertionError。
再次调用 mock 上的 calculate_length() 方法,看看 assert_called_once() 断言是如何失败的。
>>> mock.calculate_length(3, 6)
<Mock name='mock.calculate_length()' id='140398188475584'>
>>> mock.calculate_length.assert_called_once()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/opt/anaconda3/lib/python3.8/unittest/mock.py", line 892, in assert_called_once
raise AssertionError(msg)
AssertionError: Expected 'calculate_length' to have been called once. Called 2 times.
Calls: [call(3, 6), call(3, 6)].
Python 解释器希望该方法被调用一次,但它却被调用了 2 次。
如果断言与调用 mock 时使用的参数不匹配(例如,在这种情况下是 4 和 6,而不是 3 和 6),还会出现 AssertionError。
>>> mock.calculate_length.assert_called_with(4, 6)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/opt/anaconda3/lib/python3.8/unittest/mock.py", line 913, in assert_called_with
raise AssertionError(_error_message()) from cause
AssertionError: expected call not found.
Expected: calculate_length