python: clear / reset ' @lru_cache ' functool缓存每个pytest的测试用例



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

在下面的代码中,测试test_1test_2可以独立于每个over ($ 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

# 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

# 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

就用pytest-antilru。

pip install pytest-antilru

你可以走了

下面的方法定义了一个@decorator,当clear到达一个新的testcase时,它将作为cache用于修饰函数。

my_lib_fixed.py

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

# 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个命令都工作了:

$ python test_main.py
$ pytest test_main_fixed.py::test_1
$ pytest test_main_fixed.py::test_2
$ pytest test_main_fixed.py

最新更新