Python 在pytest中模拟模块级函数

Python 在pytest中模拟模块级函数,python,module,mocking,pytest,decorator,Python,Module,Mocking,Pytest,Decorator,我有一个功能,有一个装饰。decorator接受参数,并且参数的值是从另一个函数调用派生的 示例.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' 其他的 de

我有一个功能,有一个装饰。decorator接受参数,并且参数的值是从另一个函数调用派生的

示例.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'
其他的

def get_value(key):
    data = {
        'cache_ttl': 10,
    }
    # Let's assume here we launch a shuttle to the space too.
    return data[key]
我想模拟调用
get\u value()
。我在测试中使用了以下内容:

示例_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\u get\u value
来测试我的函数。但是,由于在第一次导入时调用了我的decorator,
get\u value()
会立即被调用。是否有办法在使用pytest立即导入模块之前模拟对
get_value()
的调用?

使用测试函数中的
从示例导入my_func
移动到
中。在它真正来自的地方进行修补,
other.get\u value
。这可能就是一切


Python将模块缓存在
sys.modules
中,因此模块级代码(如函数定义)仅在从任何地方首次导入时运行。如果这不是第一次,您可以使用
importlib.reload()
或删除
sys.modules
中的相应键并再次导入来强制重新导入

请注意,重新导入模块可能会产生副作用,并且您可能还希望在运行测试后再次重新导入模块,以避免干扰其他测试。如果另一个模块正在使用重新导入的模块中定义的对象,这些对象不仅会消失,而且可能不会按预期的方式更新。例如,重新导入模块可能会创建第二个本应为单例的实例

一种更可靠的方法是将原始导入的模块对象保存到其他地方,从
sys.modules
中删除,在测试期间使用补丁版本重新导入,然后在测试后将原始导入放回
sys.modules
。您可以在
sys.modules
上的
patch.dict()上下文中导入

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'

另一种可能是在测试中自己在原始函数上调用decorator。如果装饰程序使用了
functools.wrapps()
/
functools.update\u wrapper()
,则原始函数应作为
\uuuuuuuu wrapped\uuu
属性可用。这可能不可用,具体取决于装饰器的实现方式