Python 模拟导入
在这篇 Python 文章中,我们将研究 mock 库并学习如何有效地使用它。 我们将从简单的示例开始,然后查看更高级的用法。
我们将学习模拟对象和模拟的用途和陷阱。
Python 模拟导入
Python 的模拟库是最流行的单元测试库之一。 它允许我们用模拟对象替换系统的某些部分,并断言它们按预期使用。
模拟是一个强大的工具,但它经常被误解。 本文将讨论模拟是什么、如何使用它以及一些常见的陷阱。
在 Python 中模拟
模拟是用假对象替换真实对象的过程。 假对象称为模拟对象。
Mocking 允许我们测试我们的代码如何与我们系统的其他部分交互,而无需实际依赖那些其他部分。 例如,我们可以模拟一个数据库来测试我们的代码如何在没有数据库的情况下与其交互。
如何在 Python 中使用 Mock
模拟可以用于两个不同的目的:
-
测试我们代码的行为
例如,您可以模拟一个数据库来断言我们的代码正在正确查询它。
-
消除我们不想测试的行为
例如,我们可能会模拟一个数据库以避免连接到它。
我们使用模拟的目的将决定我们如何使用它。
测试行为
当我们使用模拟来测试行为时,我们想要断言我们的代码按预期与模拟对象交互。 模拟对象应该与真实对象具有相同的接口,这样我们的代码就不知道它是一个模拟对象。
我们可以使用 assert_called_with()
方法来断言模拟方法是用预期的参数调用的。 例如,如果我们正在模拟数据库,我们可能会断言使用正确的 SQL 调用了 query()
方法。
我们还可以使用 assert_called()
方法来断言调用了模拟方法。 当我们不关心参数或者参数复杂且难以断言时,这很有用。
剔除行为
当我们使用模拟来消除行为时,我们希望配置模拟对象以返回您期望的值。 例如,如果我们正在模拟一个数据库,我们可能会配置 query() 方法以返回一个虚拟数据列表。
我们可以使用 side_effect 属性来配置模拟对象。 side_effect 可以是任何值,包括函数。
调用模拟时,将返回 side_effect。
例如,我们可以使用 side_effect 来在每次调用 mock 时返回不同的值。 这对于模拟错误或不同的行为很有用。
我们还可以使用 side_effect 来引发异常。 这对于模拟错误很有用。
Python 模拟的常见陷阱
使用模拟时有一些常见的陷阱:
-
一个陷阱是试图嘲笑太多。 模拟是一个强大的工具,但它不是灵丹妙药。
当我们测试代码的行为而不是整个系统的行为时,模拟最有用。
如果我们尝试模拟太多,我们最终会得到很多模拟对象,我们的测试将难以维护。 只模拟需要的对象并为其余部分使用真实对象更好。
-
另一个陷阱是在不合适的时候使用模拟。 当您测试代码的独立部分时,模拟最有用。
如果您要测试整个系统,通常最好使用集成测试。 集成测试是对整个系统进行测试的测试,它们运行起来更慢、成本更高,但更准确。
- 最后,一个常见的错误是当我们应该使用假货时使用模拟。 假对象是模仿真实对象但没有相同接口的对象。
例如,假数据库可能会返回硬编码数据,而不是连接到真实数据库。 假货有助于杜绝行为,但它们对测试行为没那么有用。
Python 中 Mock 的基本用法
mock 最基本的用法是用 mock 对象替换一个对象。
例如,假设您有一个函数,它接受一个对象作为参数并用它做一些事情。 也许它会打印对象的名称。
示例代码:
def print_name(obj):
print(obj.name)
如果我们想测试这个函数,我们可以创建一个具有名称属性的对象并将其传递给函数。
示例代码:
class TestObject:
def __init__(self, name):
self.name = name
obj = TestObject('Abid')
print_name(obj)
输出:
Abid
代码有效,我们可以看到名称打印为 Abid。 但它有一些缺点。
首先,它要求我们创建一个仅用于测试的真实对象。 在这个简单的示例中,这可能不是什么大问题,但在更复杂的系统中,设置用于测试的必要对象可能需要大量工作。
其次,这种方式不是很灵活。 为了测试不同的行为,我们必须每次都创建一个新的真实对象。
例如,如果我们想测试当对象没有名称属性时会发生什么?
示例代码:
class TestObject:
def __init__(self, name):
self.name = name
obj = TestObject( )
print_name(obj)
对于真实的对象,我们会得到一个错误。 但是使用测试对象,代码的输出将是代码中缺少的。
输出:
__init__() missing 1 required positional argument: 'name'
如何使用 Python 模拟导入
为了使用模拟导入进行测试,让我们创建并探索一个模拟对象。
首先,我们需要导入模拟库。 模拟库将为我们提供模拟类,我们可以从中创建模拟对象。
导入库后,我们将调用我们的模拟类并打印它以查看这个模拟对象的外观。
示例代码:
from unittest.mock import Mock
mock = Mock()
print(mock)
输出:
<Mock id='139754809551888'>
代码的输出显示了模拟对象的表示,ID id='139754809551888' 一串数字。
现在让我们探讨一下我们可以用这个模拟对象做什么以及如何使用它。 好吧,模拟对象通常用于修补我们代码中的其他对象。
当我们说补丁时,它意味着替换、模仿或模拟。 他们都是一样的。
请记住,模拟的本质是替换尽可能接近真实的东西。 另外,请记住,在运行我们的模拟测试时,我们希望它处于受控环境中。
因此,外部礼仪不会使我们的模拟测试失败。
假设我们的代码中有一个外部依赖项。 我们以 json 作为外部依赖的例子。
而且它很难控制并且具有我们不一定希望在我们的测试中发生的行为。
我们可以做的是使用我们的模拟对象来修补这种依赖关系。 所以,首先,我们需要导入json格式的文件。
然后我们将使用 JSON 的 json.dumps 方法并将字典与其关联。 所以我们只是使用外部依赖方法中的一个方法。
然后我们将使用 json = mock 来修补 JSON,并且 mock 对象没有任何 dumbs 对象。 为了证明这一点,我们需要打印json的目录。
示例代码:
import json
data = json.dumps({'a':1})
json = mock
print(dir(json))
输出:
['assert_any_call', 'assert_called', 'assert_called_once', 'assert_called_once_with', 'assert_called_with', 'assert_has_calls', 'assert_not_called', 'attach_mock', 'call_args', 'call_args_list', 'call_count', 'called', 'configure_mock', 'dumps', 'getdoc', 'method_calls', 'mock_add_spec', 'mock_calls', 'reset_mock', 'return_value', 'side_effect']
我们可以将对象和方法的字符串视为与模拟对象关联的输出。 并注意 dumps 不是代码输出中的方法之一。
测试中的 Python 依赖
如果我们正在为其编写单元测试的函数使用任何依赖项,例如请求模块或日期时间。 那么我们的单元测试有可能不会总是从被测试的函数中获得相同的输出。
假设我们有一个使用请求库并发出一些 HTTP 请求的函数。 所以现在,如果我们在没有互联网连接的情况下运行我们的单元测试,它将失败并从请求库中获取连接错误。
这就是为什么最好在受控环境中测试我们的代码以控制不可预测的依赖项,我们可以在其中用模拟对象替换对依赖项的实际调用。 这将使我们能够调整该依赖项的行为。
例如,我们可以提供一些虚拟的 HTTP 响应,而不是发出实际的 HTTP 请求,这些响应将在调用 request.get() 函数时返回。
依赖关系也被欺骗,因为模拟对象具有可以查看的有关其用户的信息。
例如,如果我们调用过,我们调用了多少次,或者我们调用了多少次特定的依赖项? 所以这可以帮助我们编写更健壮的单元测试。
因此,我们现在将探讨如何在 Python 中模拟对象以进行测试。
Python 模拟对象
我们将使用两个 Python 脚本来更好地理解 Python 模拟对象。
- roll_dice_function
- mock roll_dice_function
因此,我们将致力于生成随机值的代码。 它将有一个简单的 roll_dice_function,返回一个数字和另一个之间的整数。
我们将为我们的函数 roll_dice_function 提供这两个数字。
示例代码:
#import library
import random
#define function
def roll_dice_function():
print("The number is....")
return random.randint(5, 10)
所以我们先导入库。 然后我们使用 def 关键字定义我们的函数。
此函数返回 5 到 10 之间的整数。它使用随机模块中的 random.randint 函数。
完成 roll_dice_function 的代码后,我们将使用模拟对象编写另一段代码来测试 roll_dice_function。 让我们继续。
首先,我们需要导入 mock 的测试库。
#import library
from unittest.mock import Mock
import random
#define function
def roll_dice_function():
print("The number is....")
return random.randint(5, 10)
让我们看看它是如何工作的。 现在我们将考虑我们之前创建的 roll_dice_function 的功能。
因此,每当我们调用该函数时,我们都会得到一个从 5 到 10 的随机数。
示例代码:
#import library
from unittest.mock import Mock
import random
#define function
def roll_dice_function():
print("The number is....")
return random.randint(5, 10)
roll_dice_function()
输出:
The number is....
7
让我们再次运行代码,看看这次我们得到了什么数字。
输出:
The number is....
5
因此,每次我们调用该函数时,我们都会得到一个在 5 到 10 之间生成的随机数。
现在,我们将使用 mock 使函数在每次调用时都返回相同的值。
创建模拟对象
要创建模拟对象,我们必须创建一个在模拟库中定义的模拟类对象。
示例代码:
mock_roll_object = mock.Mock()
Now we will call the mock object mock_roll_object.
mock_roll_object = mock.Mock()
mock_roll_object
输出:
<Mock name='mock.Mock()' id='139883130268416'>
它表明当我们调用模拟对象时,我们将获得作为模拟对象的输出。 所以,这就是一般模拟对象的问题; 每当我们调用模拟对象时,我们都会得到一个模拟对象作为输出。
为了简化,我们可以在模拟对象中定义一些东西。 出于调试目的,我们可以定义名称。
示例代码:
mock_roll_object = mock.Mock(name = "mocking roll dice")
因此,当我们尝试调试单元测试时,可能会出现该名称,这可以帮助我们进行调试。
输出:
<Mock name='mocking roll dice' id='139883130347136'>
我们模拟的名称在这里是 mocking roll dice。
现在,如果我们想要返回一个特定的值,我们必须将该值赋值给 return_value。 因此,每当我们调用模拟对象时,我们都会得到相同的数字。
示例代码:
mock_roll_object = mock.Mock(name = "mocking roll dice", return_value=8)
现在我们将调用该对象。
mock_roll_object()
输出:
8
每次调用模拟对象时,我们都会得到 8。 因为我们给return_value赋了8,所以会得到相同的返回值。
我们希望您发现这篇 Python 文章有助于理解如何在 Python 中使用 Mock。
相关文章
在 Python 中计算和显示凸包
发布时间:2023/06/16 浏览次数:142 分类:Python
-
因此,凸包是指围绕凸物体形状的边界。 本教程将教您在 Python 中计算和显示一组随机点的凸包。在 Python 中计算并显示一个凸包
Python 密码哈希
发布时间:2023/06/16 浏览次数:90 分类:Python
-
我们将了解密码散列以及如何使用名为 bcrypt 的第三方库加密 salt 和 hash 密码。 我们还研究了 Python 中 hashlib 库中的不同哈希算法。Python 中使用 bcrypt 库的 Salt 和 Hash 密码
Python 中的 Collatz 序列
发布时间:2023/06/16 浏览次数:180 分类:Python
-
Collatz数列是一种以1结尾的数字序列。据说当一个数字经过一组特定的运算后,最后剩下的数字一定是1。本文将解释如何编写程序,在 Python 中找到任何给定数字的 collatz 序列。Collatz 序列背后
Python 中的最长递增子序列
发布时间:2023/06/16 浏览次数:90 分类:Python
-
我们将学习什么是子序列以及如何使用 Python 中的 n 平方方法和二分搜索方法计算数组中最长的递增子序列。
使用 Python 将文件上传到 Google 云端硬盘
发布时间:2023/06/15 浏览次数:136 分类:Python
-
本文将介绍我们如何使用 Python 将文件上传到云盘,以 Google Drive 为例。 为此,我们将使用 Google Drive API。
Python 子进程捕获输出
发布时间:2023/06/15 浏览次数:136 分类:Python
-
本文的主要目的是演示如何在 Python 中捕获、存储和显示子进程的输出。Python 子进程捕获输出 Subprocess 是一个内置的 Python 模块,预装了 Python 安装文件。
Python 子进程在运行时读取标准输出
发布时间:2023/06/15 浏览次数:129 分类:Python
-
本文的主要目的是演示如何读取在 Python 中执行的子进程的标准输出。Python 子进程在运行时读取标准输出 与许多其他内置模块一样,Subprocess 也是一个内置模块,预装了“正常”Python 安装。
使用 Python 获取 CPU 数量
发布时间:2023/06/15 浏览次数:173 分类:Python
-
CPU 可以包含单核或多核。 单核只处理一个进程,而多核同时处理多个进程。本篇文章将介绍使用 Python 程序查找 CPU 内核总数的不同方法。使用 multiprocessing 模块获取 Python 中的 CPU 数量
Python获取CPU温度
发布时间:2023/06/15 浏览次数:111 分类:Python
-
本文的主要目的是演示如何借助 Python 中的 pythonnet 库读取和显示 CPU 温度。Python获取CPU温度