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