首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >python:清除/重置`@lru_cache`函数工具缓存,为未受干扰的模拟使用每个pytest测试用例

python:清除/重置`@lru_cache`函数工具缓存,为未受干扰的模拟使用每个pytest测试用例
EN

Stack Overflow用户
提问于 2022-05-12 21:37:10
回答 1查看 427关注 0票数 0

我在代码中结合了模拟缓存mocking对于每个pytest来说都是(某种程度上)随机的,我不知道,在实际情况下将返回什么。因此,我想在不同的测试用例中使用不同的值来模拟相同的函数(在我的示例fct_child中)。但是,缓存会产生一些问题,因为返回值(在我的fct_parent示例中是缓存的,因此模拟的函数只在第一个测试用例中到达,然后由于父函数的缓存而始终跳过)。我需要找到一种方法来清除/重置pytest之间的缓存。

在下面的代码中,可以成功地独立地执行测试test_1test_2 ($ pytest test_main.py::test_1$ pytest test_main.py::test_2)。但是,如果pytest运行在完整模块($ pytest test_main.py)上,那么第二个测试就会崩溃。此外,主体部分工作($ python test_main.py),我确保缓存按预期工作。

那么,如何修复代码,使pytest在执行所有测试用例时也通过( $ pytest test_main.py场景)?

test_main.py

代码语言:javascript
复制
# test_main.py

from my_lib import fct_parent, get_n_calls_fct_child

class ChildMock:

    def __init__(self, val_child):
        self.n_calls_mock = 0
        self.val_child = val_child

    def fct(self):
        self.n_calls_mock += 1
        return self.val_child

def test_1(monkeypatch):
    """This test interacts with test_2:
    Exectuing each test independently with pytest works, executing both in one run, fails.
    This is due to the lru_cache being not cleaned.
    """
    val_child = "mocked test 1"
    child_mock = ChildMock(val_child)

    with monkeypatch.context() as mpc:
        mpc.setattr("my_lib.fct_child", child_mock.fct)  # mocks fct_child to return ret_val
        assert fct_parent() == val_child
        assert fct_parent() == val_child
        assert child_mock.n_calls_mock == 1

def test_2(monkeypatch):
    """This test interacts with test_1:
    Exectuing each test independently with pytest works, executing both in one run, fails.
    This is due to the lru_cache being not cleaned.
    """
    val_child = "mocked test 2"
    child_mock = ChildMock(val_child)

    with monkeypatch.context() as mpc:
        mpc.setattr("my_lib.fct_child", child_mock.fct)  # mocks fct_child to return ret_val
        assert fct_parent() == val_child
        assert fct_parent() == val_child
        assert child_mock.n_calls_mock == 1

if __name__ == "__main__":
    assert fct_parent() == "unmocked"
    assert fct_parent() == "unmocked"
    n_calls_fct_child = get_n_calls_fct_child()
    assert n_calls_fct_child == 1, f"{n_calls_fct_child=} should be == 1"
    print("good: fct_child was only computed once")

my_lib.py

代码语言:javascript
复制
# my_lib.py

from functools import lru_cache

_n_child_calls = 0

@lru_cache(256)
def fct_parent():
    return fct_child()

def fct_child():
    global _n_child_calls
    _n_child_calls += 1
    return "unmocked"

def get_n_calls_fct_child():
    return _n_child_calls
EN

回答 1

Stack Overflow用户

发布于 2022-05-12 21:37:10

下面的方法定义了一个@decorator,一旦到达一个新的testcaseclear就是修饰函数的cache

my_lib_fixed.py

代码语言:javascript
复制
import os
from functools import lru_cache, wraps

_pytest_cache_func = {}  # Dict {'func.__name__: name_of_pytest_with_last_caching}

_n_child_calls = 0

def lru_cache_pytest_save(*lru_cache_args, **lru_cache_kwargs):
    """like @lru_cache, but additionally clears lru_cache of this function in between pytest testcases"""

    # if you want to switch _pytest_save off:
    # def decorator(func):
    #     return lru_cache(func)
    # return decorator

    def decorator(func):
        func_cached = lru_cache(func)

        @wraps(func)
        def wrapper(*args, **kwargs):
            pytest_current = os.environ.get("PYTEST_CURRENT_TEST")
            if _pytest_cache_func.get(func_cached.__name__) != pytest_current:
                func_cached.cache_clear()
                _pytest_cache_func[func_cached.__name__] = pytest_current

            return func_cached(*args, **kwargs)

        return wrapper

    return decorator


@lru_cache_pytest_save(256)
def fct_parent():
    return fct_child()

def fct_child():
    global _n_child_calls
    _n_child_calls += 1
    return "unmocked"

def get_n_calls_fct_child():
    return _n_child_calls

def reset_n_calls_fct_child():
    global _n_child_calls
    _n_child_calls = 0

由于模块名略有不同,您需要在

test_main_fixed.py

代码语言:javascript
复制
# test_main_fixed.py

from my_lib_fixed import fct_parent, get_n_calls_fct_child

class ChildMock:

    def __init__(self, val_child):
        self.n_calls_mock = 0
        self.val_child = val_child

    def fct(self):
        self.n_calls_mock += 1
        return self.val_child

def test_1(monkeypatch):
    """This test interacts with test_2:
    Exectuing each test independently with pytest works, executing both in one run, fails.
    This is due to the lru_cache being not cleaned.
    """
    val_child = "mocked test 1"
    child_mock = ChildMock(val_child)

    with monkeypatch.context() as mpc:
        mpc.setattr("my_lib_fixed.fct_child", child_mock.fct)  # mocks fct_child to return ret_val
        assert fct_parent() == val_child
        assert fct_parent() == val_child
        assert child_mock.n_calls_mock == 1

def test_2(monkeypatch):
    """This test interacts with test_1:
    Exectuing each test independently with pytest works, executing both in one run, fails.
    This is due to the lru_cache being not cleaned.
    """
    val_child = "mocked test 2"
    child_mock = ChildMock(val_child)

    with monkeypatch.context() as mpc:
        mpc.setattr("my_lib_fixed.fct_child", child_mock.fct)  # mocks fct_child to return ret_val
        assert fct_parent() == val_child
        assert fct_parent() == val_child
        assert child_mock.n_calls_mock == 1

if __name__ == "__main__":
    assert fct_parent() == "unmocked"
    assert fct_parent() == "unmocked"
    n_calls_fct_child = get_n_calls_fct_child()
    assert n_calls_fct_child == 1, f"{n_calls_fct_child=} should be == 1"
    print("good: fct_child was only computed once")

现在,所有4个命令都可以工作了:

代码语言:javascript
复制
$ python test_main.py
$ pytest test_main_fixed.py::test_1
$ pytest test_main_fixed.py::test_2
$ pytest test_main_fixed.py
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72222314

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档