Pytest: настройка макетов с функциями без побочных эффектов - PullRequest
1 голос
/ 22 января 2020

У меня есть вопрос, связанный с ответом на этот вопрос:

pytest: настройка макета для каждой тестовой функции

Мне нравится идея использования функций, которые получают фиктивные объекты с помощью аргументов. Таким образом, настройка макетов может быть использована повторно. Из ответа я заключаю, что фиктивные объекты являются изменяемыми в 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

Возможно ли это?

1 Ответ

2 голосов
/ 23 января 2020

Я бы предложил вводить макеты, используя 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"

, и теперь вы хотите преобразовать его в тестирование на bacon и 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.TestCase s не принимает аргументы в тестовых методах. Это тот же подход, который описан в моего ответа . В приведенном ниже примере я сохраняю возвращаемое значение устройства eggs_mock в атрибуте экземпляра Tests с помощью дополнительного устройства autouse :

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"
...