Python模拟导入

在这篇Python文章中,我们将研究mock 库并学习如何有效地使用它。我们将从简单的例子开始,然后研究更高级的用法。

我们将学习mock 对象和嘲弄的用途和陷阱。

Python模拟导入

Python的mock 库是用于单元测试的最流行的库之一。它允许我们用模拟对象替换我们系统的一部分,并断言它们是按预期使用的。

嘲讽是一个强大的工具,但它经常被误解。这篇文章将讨论什么是嘲弄,如何使用它,以及一些常见的陷阱。

Python中的嘲弄

嘲弄是用一个假的对象来替换一个真实的对象的过程。这个假对象被称为mock

嘲弄允许我们测试我们的代码如何与系统的其他部分交互,而不需要实际依赖这些其他部分。例如,我们可以模拟一个数据库来测试我们的代码是如何与之交互的,而不需要一个数据库。

如何在Python中使用Mock

嘲讽可以用于两个不同的目的:

  1. 测试我们代码的行为

    例如,你可以模拟一个数据库来断言我们的代码正在正确地查询它。

  2. 剔除我们不想测试的行为

    例如,我们可以模拟一个数据库以避免连接到它。

我们使用嘲弄的目的将决定我们如何使用它。

测试行为

当我们使用嘲讽来测试行为时,我们要断言我们的代码与嘲讽对象的交互是预期的。模拟对象应该具有与真实对象相同的接口,这样我们的代码就不会知道它是一个模拟对象。

我们可以使用assert_called_with() 方法来断言一个模拟方法是以预期的参数被调用的。例如,如果我们正在模拟一个数据库,我们可以断言query() 方法是用正确的SQL调用的。

我们还可以使用assert_called() 方法来断言一个模拟方法被调用。当我们不关心参数,或者参数很复杂,难以断言时,这很有用。

存根行为

当我们使用嘲讽来存留行为时,我们要配置嘲讽对象来返回你期望的值。例如,如果我们正在模拟一个数据库,我们可以配置query() 方法来返回一个假数据的列表。

我们可以使用side_effect 属性来配置一个模拟对象。side_effect 可以是任何值,包括一个函数。

当模拟对象被调用时,side_effect 被返回。

例如,我们可以使用一个side_effect ,在每次调用模拟对象时返回不同的值。这对于模拟错误或不同的行为很有用。

我们还可以使用side_effect 来引发一个异常。这对于模拟错误是很有用的。

Python中嘲弄的常见陷阱

在使用嘲讽时有一些常见的陷阱:

  1. 一个陷阱是试图嘲弄太多。嘲讽是一个强大的工具,但它不是万能的。

    当我们测试代码的行为,而不是整个系统的行为时,嘲弄是最有用的。

    如果我们试图模拟太多,我们最终会有很多模拟对象,而我们的测试将很难维护。只模拟需要的对象,其余的使用真实的对象会更好。

  2. 另一个陷阱是在不合适的时候使用嘲弄。当你测试你的代码的孤立部分时,嘲弄是最有用的。

    如果你要测试整个系统,通常最好使用集成测试。集成测试是锻炼整个系统的测试,它们运行起来更慢,更昂贵,但更准确。

  3. 最后,一个常见的错误是,当我们应该使用假的时候,却使用了mocks。一个假的对象是一个模仿真实对象的对象,但没有相同的接口。

例如,一个假的数据库可能会返回硬编码的数据而不是连接到一个真正的数据库。赝品可以帮助存根出行为,但它们对于测试行为并不那么有用。

Python中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模拟导入

为了使用模拟导入进行测试,让我们创建并探索一个模拟对象。

首先,我们需要导入mock 库。mock 库将给我们提供mock 类,我们可以从该类中制作我们的模拟对象。

导入该库后,我们将调用我们的mock 类并打印出来,看看这个模拟对象是什么样子的。

示例代码:

from unittest.mock import Mock
mock = Mock()
print(mock)

输出:

<Mock id='139754809551888'>

代码的输出显示了模拟对象的表示,IDid='139754809551888' ,是一串数字。

现在让我们来探讨一下我们能用这个模拟对象做什么以及如何使用它。好吧,模拟对象通常被用来修补我们代码中的其他对象。

当我们说补丁时,它意味着替换、模仿或模拟。它们都是一样的。

请记住,模拟的本质是尽可能地替换一些与真实事物相近的东西。另外,请记住,当运行我们的模拟测试时,我们希望它是在一个受控的环境中。

所以,外部的决定性因素不会使我们的模拟测试失败。

假设我们的代码中有一个外部依赖性。让我们用json 作为外部依赖性的一个例子。

它很难控制,而且有一些我们不一定想在我们的测试中发生的行为。

我们可以做的是使用我们的模拟对象来修补这个依赖关系。因此,首先,我们需要导入json 文件格式。

然后我们将使用JSON的json.dumps 方法,并将一个字典与之关联。所以我们只是使用一个来自外部依赖关系方法的方法。

然后我们将使用json = mock ,对JSON进行修补,模拟对象没有任何字典对象。为了证明这一点,我们需要打印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依赖性

如果我们为一个函数编写单元测试时使用了任何依赖性,例如request 模块或日期时间。那么我们的单元测试就有可能不总是从被测试的函数中获得相同的输出。

比方说,我们有一个使用request 库的函数,并提出一些HTTP请求。所以现在,如果我们在没有互联网连接的情况下运行我们的单元测试,它就会失败,并从request 库中得到一个连接错误。

这就是为什么最好在受控环境中测试我们的代码,以获得对不可预测的依赖关系的控制,我们可以用一个模拟对象代替对依赖关系的实际调用。这将使我们能够调整该依赖关系的行为。

例如,我们可以提供一些假的HTTP响应,而不是进行实际的HTTP请求,这些响应将在调用request.get() 函数时返回。

依赖关系也是可以欺骗的,因为模拟对象有关于其用户的信息,可以被查看。

比如说,如果我们调用了,我们调用了多少次,或者我们调用了某个依赖关系多少次?所以,这可以帮助我们写出我们更强大的单元测试。

所以,我们现在将探讨如何在Python中模拟对象进行测试。

Pythonmock 对象

我们将在两个Python脚本上工作,以更好地理解Python模拟对象。

  1. roll_dice_function
  2. mockroll_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 模块中的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 对象

要创建一个mock 对象,我们必须创建一个在mock 库中定义的mock类对象。

示例代码:

mock_roll_object =  mock.Mock()

现在我们将调用模拟对象mock_roll_object

mock_roll_object =  mock.Mock()
mock_roll_object

输出:

<Mock name='mock.Mock()' id='139883130268416'>

这表明,当我们调用模拟对象时,我们将得到模拟对象的输出。所以,这就是关于模拟对象的一般情况;只要我们调用模拟对象,我们就会得到一个模拟对象作为输出。

为了简化,我们可以在模拟对象中定义一些东西。为了调试的目的,我们可以定义name

示例代码:

mock_roll_object =  mock.Mock(name = "mocking roll dice")

所以,当我们试图调试我们的单元测试时,这个名字可能会出现,这可以帮助我们调试。

输出:

<Mock name='mocking roll dice' id='139883130347136'>

我们的mock的名字在这里是mocking roll dice

现在,如果我们想返回一个特定的值,我们将不得不把这个值分配给return_value 。因此,我们每次调用模拟对象都会得到相同的数字。

示例代码:

mock_roll_object =  mock.Mock(name = "mocking roll dice", return_value=8)

现在我们将调用该对象。

mock_roll_object()

输出:

8

每次调用模拟对象时,我们都会得到8。因为我们已经给return_value ,我们将得到相同的返回值。

我们希望这篇Python文章对你了解如何在Python中使用Mock有帮助。