patch.multiple 可以作为装饰器与 pytest 一起使用吗

问题描述 投票:0回答:2

我有一个从

https://docs.python.org/3/library/unittest.mock.html#patch-multiple
采用的test_tmp.py

from unittest.mock import DEFAULT, MagicMock, patch

thing = object()
other = object()

@patch.multiple('__main__', thing=DEFAULT, other=DEFAULT)
def test_function(thing, other):
    print(f'thing={thing}')
    print(f'other={other}')
    assert isinstance(thing, MagicMock)
    assert isinstance(other, MagicMock)

test_function()

它与Python一起运行

python test_tmp.py
thing=<MagicMock name='thing' id='4355085552'>
other=<MagicMock name='other' id='4355243312'>

但它不适用于 pytest,并出现类似错误

pytest test_tmp.py
============================================================================================================= test session starts =============================================================================================================
platform darwin -- Python 3.8.2, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: /private/tmp
collected 0 items / 1 error

=================================================================================================================== ERRORS ====================================================================================================================
________________________________________________________________________________________________________ ERROR collecting test_tmp.py _________________________________________________________________________________________________________
test_tmp.py:13: in <module>
    test_function()
/Users/user/.pyenv/versions/3.8.2/lib/python3.8/unittest/mock.py:1345: in patched
    with self.decoration_helper(patched,
/Users/user/.pyenv/versions/3.8.2/lib/python3.8/contextlib.py:113: in __enter__
    return next(self.gen)
/Users/user/.pyenv/versions/3.8.2/lib/python3.8/unittest/mock.py:1313: in decoration_helper
    arg = patching.__enter__()
/Users/user/.pyenv/versions/3.8.2/lib/python3.8/unittest/mock.py:1416: in __enter__
    original, local = self.get_original()
/Users/user/.pyenv/versions/3.8.2/lib/python3.8/unittest/mock.py:1389: in get_original
    raise AttributeError(
E   AttributeError: <module '__main__' from '/path/to/bin/pytest'> does not have the attribute 'thing'
=========================================================================================================== short test summary info ===========================================================================================================
ERROR test_tmp.py - AttributeError: <module '__main__' from '/path/to/bin/pytest'> does not have the attribute 'thing'
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
============================================================================================================== 1 error in 0.31s ===============================================================================================================

想知道为什么吗?

我正在使用 pytest = "^5.4.3"

python pytest python-mock
2个回答
6
投票

这里有一些东西在 pytest 中不起作用:

  • 您无法直接调用测试,因此调用该函数将不起作用
  • 装饰器版本在这种情况下不起作用,我猜
    pytest
    只是没有实现这个(pytest默认将测试函数参数理解为固定装置;它正确处理
    patch
    patch.object
    使用的位置参数,但似乎无法处理
    patch.multiple
    )
  • 注入的关键字参数
  • 使用
    '__main__'
    可能不起作用,您可以使用
    sys.modules[__name__]
    代替。

所以这是一个工作版本:

def test_function1():
    with patch.multiple(sys.modules[__name__],
                        thing=DEFAULT,
                        other=DEFAULT) as mocks:
        print(f'thing = {mocks["thing"]}')
        print(f'other = {mocks["other"]}')
        assert isinstance(thing, MagicMock)
        assert isinstance(other, MagicMock)

此版本也应该与

unittest
一起使用。

在 pytest 中,您通常会将这种模拟移至固定装置中,因此这可能更符合 pytest 精神:

@pytest.fixture
def multiple():
    with patch.multiple(sys.modules[__name__],
                        thing=DEFAULT,
                        other=DEFAULT) as mocks:
        yield mocks


def test_function(multiple):
    print(f'thing = {multiple["thing"]}')
    print(f'other = {multiple["other"]}')
    assert isinstance(thing, MagicMock)
    assert isinstance(other, MagicMock)

注:
这只是回答了标题中的问题,而不是“为什么”——我查看了

patch.multiple
的源代码,但不明白它如何与 pytest 交互。也许有更深入了解的人可以回答这个问题。


0
投票

在比赛中你可以执行以下任一操作:

import pytest
from unittest.mock import patch

@pytest.fixture
def my_fixture():
    # Patching the first function
    with patch('module.function1') as mock_function1:
        mock_function1.return_value = 'Mocked result for function1'

        # Patching the second function
        with patch('module.function2') as mock_function2:
            mock_function2.return_value = 'Mocked result for function2'

            # Yield the fixture
            yield mock_function1, mock_function2

# Test using the fixture
def test_my_fixture(my_fixture):
    mock_function1, mock_function2 = my_fixture

    # Test code using the mocked functions
    result1 = module.function1()  # This will use the mock
    result2 = module.function2()  # This will use the other mock

    assert result1 == 'Mocked result for function1'
    assert result2 == 'Mocked result for function2'

或者你可以做两个单独的装置并将它们都传递到另一个函数中。

@pytest.fixture(scope="function")
def patched_check():
    with patch('core.management.commands.wait_for_db.Command.check') as p:
        yield p
    logging.info("patched_check finished")

@pytest.fixture(scope="function")
def time_sleep():
    with patch('time.sleep') as p:
        yield p
    logging.info("patched_check finished")

def wait_delay(patched_check, time_sleep):
    pass

第二个例子来自我的代码,它模拟了

time.sleep
core.management.commands.wait_for_db.Command.check

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.