Пересмешивание функции уровня модуля в pytest - PullRequest
0 голосов
/ 26 июня 2019

У меня есть функция, у которой есть декоратор. Декоратор принимает аргументы, и значение аргумента получается из другого вызова функции.

example.py

from cachetools import cached
from cachetools import TTLCache

from other import get_value


@cached(cache=TTLCache(maxsize=1, ttl=get_value('cache_ttl')))
def my_func():
    return 'result'

other.py

def get_value(key):
    data = {
        'cache_ttl': 10,
    }
    # Let's assume here we launch a shuttle to the space too.
    return data[key]

Я бы хотел посмеяться над звонком на get_value(). В своем тесте я использую следующее:

example_test.py

import mock
import pytest

from example import my_func


@pytest.fixture
def mock_get_value():
    with mock.patch(
        "example.get_value",
        autospec=True,
    ) as _mock:
        yield _mock


def test_my_func(mock_get_value):
    assert my_func() == 'result'

Здесь я добавляю mock_get_value в test_my_func. Однако, поскольку мой декоратор вызывается при первом импорте, get_value() вызывается немедленно. Любая идея, если есть способ смоделировать вызов get_value(), прежде чем модуль будет импортирован сразу с помощью pytest?

1 Ответ

1 голос
/ 26 июня 2019

Переместите from example import my_func внутрь вашего with в вашей функции тестирования. Также исправьте это, где это действительно происходит, other.get_value. Это может быть все, что нужно.


Python кэширует модули в sys.modules, поэтому код уровня модуля (например, определения функций) запускается только при первом импорте из в любом месте . Если это не первый раз, вы можете принудительно выполнить повторный импорт, используя importlib.reload() или удалив соответствующий ключ в sys.modules и импортировав снова.

Помните, что повторный импорт модуля может иметь побочные эффекты, и вы также можете захотеть повторно импортировать модуль после запуска теста, чтобы не мешать другим тестам. Если другой модуль использовал объекты, определенные в повторно импортированном модуле, они не просто исчезают и не могут быть обновлены так, как он ожидает. Например, повторный импорт модуля может создать второй экземпляр того, что должно было быть одиночным.

Еще один надежный подход - сохранить исходный импортированный объект модуля в другом месте, удалить из sys.modules, повторно импортировать с исправленной версией на время теста, а затем вернуть исходный импорт в sys.modules после тест. Вы можете сделать это с помощью импорта внутри контекста patch.dict() на sys.modules.

import mock
import sys

import pytest

@pytest.fixture
def mock_get_value():
    with mock.patch(
        "other.get_value",
        autospec=True,
    ) as _mock, mock.patch.dict("sys.modules"):
        sys.modules.pop("example", None)
        yield _mock


def test_my_func(mock_get_value):
    from example import my_func
    assert my_func() == 'result'

Еще одна возможность - вызвать декоратор самостоятельно в тесте по исходной функции. Если декоратор использовал functools.wraps() / functools.update_wrapper(), то оригинальная функция должна быть доступна как атрибут __wrapped__. Это может быть недоступно в зависимости от того, как был реализован декоратор.

...