Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/azure/13.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 用pytest模拟导入的函数_Python_Mocking_Pytest_Magicmock - Fatal编程技术网

Python 用pytest模拟导入的函数

Python 用pytest模拟导入的函数,python,mocking,pytest,magicmock,Python,Mocking,Pytest,Magicmock,我想测试一个我写的电子邮件发送方法。在文件中,格式化\u email.py I import发送\u email from cars.lib.email import send_email class CarEmails(object): def __init__(self, email_client, config): self.email_client = email_client self.config = config def s

我想测试一个我写的电子邮件发送方法。在文件中,格式化\u email.py I import发送\u email

 from cars.lib.email import send_email

 class CarEmails(object):

    def __init__(self, email_client, config):
        self.email_client = email_client
        self.config = config

    def send_cars_email(self, recipients, input_payload):
在send_cars_email()中格式化电子邮件内容后,我使用先前导入的方法发送电子邮件

 response_code = send_email(data, self.email_client)
在我的测试文件test\u car\u emails.py中

@pytest.mark.parametrize("test_input,expected_output", test_data)
def test_email_payload_formatting(test_input, expected_output):
    emails = CarsEmails(email_client=MagicMock(), config=config())
    emails.send_email = MagicMock()
    emails.send_cars_email(*test_input)
    emails.send_email.assert_called_with(*expected_output)
当我运行测试时,它在未调用断言时失败。我相信问题是我在嘲笑发送电子邮件的功能


我应该在哪里模拟此函数?

您正在用行
emails.send\u email=MagicMock()
模拟的是该函数

class CarsEmails:

    def send_email(self):
        ...
你没有。因此,此行只会向
电子邮件
对象添加一个新函数。但是,此函数不是从代码中调用的,赋值将完全无效。相反,您应该模拟来自
cars.lib.email
模块的
send_email
功能

模拟使用它的函数 一旦您在模块
format\u email.py
中通过
从cars.lib.email import send\u email
导入功能
send\u email
,该功能将以
format\u email.send\u email
的名称提供。因为您知道函数是在那里调用的,所以可以用它的新名称模拟它:

from unittest.mock import patch

from format_email import CarsEmails

@pytest.mark.parametrize("test_input,expected_output", test_data)
def test_email_payload_formatting(config, test_input, expected_output):
    emails = CarsEmails(email_client=MagicMock(), config=config)
    with patch('format_email.send_email') as mocked_send:
        emails.send_cars_email(*test_input)
        mocked_send.assert_called_with(*expected_output)
模拟已定义的函数 更新

阅读
unittest
文档中的部分确实很有帮助(另请参阅建议它的部分):

基本原则是,在查找对象的位置进行修补,这不一定与定义对象的位置相同

因此,请坚持在使用位置模拟函数,不要从刷新导入或按正确顺序对齐导入开始。即使当
format_email
的源代码由于某种原因无法访问时(例如,当它是一个cythonized/compiled C/C++扩展模块时),您仍然只有两种可能的导入方法,因此,只需尝试中描述的两种模拟可能性,并使用成功的一种

原始答案

您还可以在其原始模块中模拟
发送电子邮件
功能:

with patch('cars.lib.email.send_email') as mocked_send:
    ...
但是请注意,如果您在打补丁之前调用了
format\u email.py
中的导入
send\u email
,那么打补丁
cars.lib.email
将不会对
format\u email
中的代码产生任何影响,因为该函数已经导入,因此下面示例中的
mock\u send
将不会被调用:

from format_email import CarsEmails

...

emails = CarsEmails(email_client=MagicMock(), config=config)
with patch('cars.lib.email.send_email') as mocked_send:
    emails.send_cars_email(*test_input)
    mocked_send.assert_called_with(*expected_output)
要解决这个问题,您应该在
cars.lib.email
补丁后第一次导入
format\u email

with patch('cars.lib.email.send_email') as mocked_send:
    from format_email import CarsEmails
    emails = CarsEmails(email_client=MagicMock(), config=config)
    emails.send_cars_email(*test_input)
    mocked_send.assert_called_with(*expected_output)
或者重新加载模块,例如使用
importlib.reload()


如果你问我的话,这两种方式都不太好。我坚持在调用函数的模块中模拟函数。

最简单的修复方法如下

@pytest.mark.parametrize("test_input,expected_output", test_data)
def test_email_payload_formatting(test_input, expected_output):
    emails = CarsEmails(email_client=MagicMock(), config=config())
    import format_email
    format_email.send_email = MagicMock()
    emails.send_cars_email(*test_input)
    format_email.send_email.assert_called_with(*expected_output)
基本上,您有一个模块已经导入了
格式的
发送电子邮件
,您现在必须更新加载的模块

但这不是最推荐的方式,因为您失去了原来的
send\u email
功能。因此,您应该将补丁与上下文一起使用。有不同的方法可以做到这一点

方式1

from format_email import CarsEmails

@pytest.mark.parametrize("test_input,expected_output", test_data)
def test_email_payload_formatting(test_input, expected_output):
    emails = CarsEmails(email_client=MagicMock(), config=config())
    with patch('cars.lib.email.send_email') as mocked_send:
        import format_email
        reload(format_email)
        emails.send_cars_email(*test_input)
        mocked_send.assert_called_with(*expected_output)
在本文中,我们模拟导入的实际函数

方式2

with patch('cars.lib.email.send_email') as mocked_send:
    from format_email import CarsEmails

    @pytest.mark.parametrize("test_input,expected_output", test_data)
    def test_email_payload_formatting(test_input, expected_output):
        emails = CarsEmails(email_client=MagicMock(), config=config())
        emails.send_cars_email(*test_input)
        mocked_send.assert_called_with(*expected_output)
这样,文件中的任何测试也将使用补丁函数进行其他测试

方式3

from format_email import CarsEmails

@pytest.mark.parametrize("test_input,expected_output", test_data)
def test_email_payload_formatting(test_input, expected_output):
    with patch('format_email.send_email') as mocked_send:
        emails = CarsEmails(email_client=MagicMock(), config=config())
        emails.send_cars_email(*test_input)
        mocked_send.assert_called_with(*expected_output)
在这个方法中,我们修补导入本身,而不是调用的实际函数。在这种情况下,不需要重新加载


因此,您可以看到有不同的模拟方法,有些方法是良好的实践,有些是个人选择

因为您使用的是pytest,我建议您使用pytest的 内置夹具

考虑以下简单设置:

我们定义要模拟的函数。

在一个单独的文件中,调用函数的类。

我们使用“monkeypatch”夹具模拟函数的使用位置

如果您想重用它,我们还可以将模拟分解到它自己的夹具中。


什么是电子邮件。发送电子邮件?它是
CarEmails
类的一部分,还是指
cars.lib.email.send\u email
?它指的是cars.lib.email.send\u email我明白了,但它不会是emails对象的一部分,是吗?不会。我尝试从cars.lib.email import send_email导入
,然后在测试文件中导入
send_email=MagicMock()
,但它也没有被称为错误。我知道我误解了模拟的工作原理。我只是不确定我应该在哪里模仿。你永远不应该需要
importlib.reload()
。只需修补正确的位置。另请参阅
unittest.mock
文档:.@MartijnPieters dang,再次阅读
unittest
apidoc的时间到了。感谢您提供的链接,让我更新提及它的答案。不要将
MagicMock()
实例直接分配给要模拟的对象,请始终使用。否则模拟将不会被清理,您的测试框架将渗入其他测试并导致问题。您的方式2也不正确。那里的补丁程序在定义了测试函数之后就存在了,因此在实际测试运行时不会应用模拟。您的方法3是唯一正确的方法,但您对它的解释却不正确。考虑到hoefling已经涵盖了这个选项,这是相当多余的。@MartijnPieters,我希望-1不是您提供的,因为我已经提到“但这不是MagicMock最推荐的方式”,我只是想向海报展示为什么它没有像他尝试的方式那样工作。在发布之前,我已经测试了所有3种方法。我从来没有使用过方法1,在测试时不需要重新加载模块。如果模块全局变量在运行测试(或)时发生更改,请重构代码
from format_email import CarsEmails

@pytest.mark.parametrize("test_input,expected_output", test_data)
def test_email_payload_formatting(test_input, expected_output):
    with patch('format_email.send_email') as mocked_send:
        emails = CarsEmails(email_client=MagicMock(), config=config())
        emails.send_cars_email(*test_input)
        mocked_send.assert_called_with(*expected_output)
"""`my_library.py` defining 'foo'."""


def foo(*args, **kwargs):
    """Some function that we're going to mock."""
    return args, kwargs
"""`my_module` defining MyClass."""
from my_library import foo


class MyClass:
    """Some class used to demonstrate mocking imported functions."""
    def should_call_foo(self, *args, **kwargs):
        return foo(*args, **kwargs)
"""`test_my_module.py` testing MyClass from 'my_module.py'"""
from unittest.mock import Mock

import pytest

from my_module import MyClass


def test_mocking_foo(monkeypatch):
    """Mock 'my_module.foo' and test that it was called by the instance of
    MyClass.
    """
    my_mock = Mock()
    monkeypatch.setattr('my_module.foo', my_mock)

    MyClass().should_call_foo(1, 2, a=3, b=4)

    my_mock.assert_called_once_with(1, 2, a=3, b=4)
@pytest.fixture
def mocked_foo(monkeypatch):
    """Fixture that will mock 'my_module.foo' and return the mock."""
    my_mock = Mock()
    monkeypatch.setattr('my_module.foo', my_mock)
    return my_mock


def test_mocking_foo_in_fixture(mocked_foo):
    """Using the 'mocked_foo' fixture to test that 'my_module.foo' was called
    by the instance of MyClass."""
    MyClass().should_call_foo(1, 2, a=3, b=4)

    mocked_foo.assert_called_once_with(1, 2, a=3, b=4)