首页 > 解决方案 > Pytest:使用没有副作用的函数设置模拟

问题描述

我有一个与这个问题的答案相关的问题:

pytest:为每个测试函数设置一个模拟

我喜欢使用通过参数接收模拟对象的函数的想法。这样,mock 的设置就可以重用了。我从答案中得出结论,模拟对象在 Python 中是可变的,并且在函数内部更改它们将产生它们在外部更改的副作用。但是,我认为产生副作用是危险的。所以,我建议如下:

def test(self, mock1):
    mock1 = setup_mock1_to_always_xxx(mock1)
    ...

def setup_mock1_to_always_xxx(mock1):
    # create a copy to avoid side effects
    mock1 = mock1.copy() # how to copy a Mock?
    mock1.return_value = "my return value"
    return mock1

这可能吗?

标签: pythonmockingpytest

解决方案


我建议使用pytest固定装置而不是手动模拟复制来注入模拟。功能范围的固定装置(默认固定装置)为每个测试重新评估。示例:假设您有一个模块

# mod.py

def spam():
    return eggs()


def eggs():
    return "eggs"

和一个测试

from unittest.mock import patch
from mod import spam


@patch("mod.eggs")
def test_bacon(mock1):
    mock1.return_value = "bacon"
    assert spam() == "bacon"

现在您想将其重构为针对baconand进行测试bacon with eggs。移出夹具内的补丁:

import pytest
from unittest.mock import patch
from mod import spam


@pytest.fixture
def eggs_mock():
    with patch("mod.eggs") as mock1:
        yield mock1


def test_bacon(eggs_mock):
    eggs_mock.return_value = "bacon"
    assert spam() == "bacon"


def test_bacon_with_eggs(eggs_mock):
    eggs_mock.return_value = "bacon with eggs"
    assert spam() == "bacon with eggs"

您现在有两个不同的mod.eggs函数模拟,每个测试中都有一个唯一的模拟。

unittest风格的测试

这种方法也适用于unittest测试类,尽管注入有点冗长,因为unittest.TestCases 不接受测试方法中的参数。这与我的这个答案中描述的方法相同。在下面的示例中,我通过使用附加的autouse 固定装置eggs_mock将固定装置返回值存储在实例属性中:Tests

from unittest import TestCase
from unittest.mock import patch
import pytest
from mod import spam


@pytest.fixture
def eggs_mock():
    with patch("mod.eggs") as mock1:
        yield mock1


class Tests(TestCase):

    @pytest.fixture(autouse=True)
    def inject_eggs_mock(self, eggs_mock):
        self._eggs_mock = eggs_mock

    def test_bacon(self):
        self._eggs_mock.return_value = "bacon"
        assert spam() == "bacon"


    def test_bacon_with_eggs(self):
        self._eggs_mock.return_value = "bacon with eggs"
        assert spam() == "bacon with eggs"

推荐阅读