使用 lru_cache 装饰器测试函数
Test function with lru_cache decorator
我正在尝试测试一种通过 lru_cache
记住的方法(因为它是一个昂贵的数据库调用)。 pytest-mock
.
代码的简化版本是:
class User:
def __init__(self, file):
# load a file
@lru_cache
def get(self, user_id):
# do expensive call
那我在测试:
class TestUser:
def test_get_is_called(self, mocker):
data = mocker.ANY
user = User(data)
repository.get(user_id)
open_mock = mocker.patch('builtins.open', mocker.mock_open())
open_mock.assert_called_with('/foo')
但我收到以下错误:
TypeError: unhashable type: '_ANY'
发生这种情况是因为 functools.lru_cache
需要存储的键是 可散列的 ,即实现方法 __hash__
或 __cmp__
。
如何在 mocker 中模拟这些方法以使其工作?
我试过了
user.__hash__.return_value = 'foo'
运气不好。
而不是使用 mocker.ANY
(意在断言中用作等于任何对象的占位符的对象)我相信您反而想使用哨兵对象(例如 mocker.sentinel.DATA
).
这似乎在快速测试中有效:
from functools import lru_cache
@lru_cache(maxsize=None)
def f(x):
return (x, x)
def test(mocker):
ret = f(mocker.sentinel.DATA)
assert ret == (mocker.sentinel.DATA, mocker.sentinel.DATA)
对于到达这里试图找出如何测试用 lru_cache
或 alru_cache
装饰的函数的人来说,答案是在每次测试之前 。
这可以按如下方式完成:
def setup_function():
"""
Avoid the `(a)lru_cache` causing tests with identical parameters to interfere
with one another.
"""
my_cached_function.cache_clear()
如何在 运行ning pytest
时关闭 @lru_cache
万一你因为想测试一个 @lru_cache
- 带有不同模拟的修饰函数(但是 lru_cache 阻止你的模拟)而结束了这里......
如果你 运行 pytest!
,只需将 @lru_cache
的 maxsize
设置为 0
@lru_cache(maxsize=0 if "pytest" in sys.modules else 256)
最小工作示例
with @lru_cache active (maxsize=256) when code 运行s and deactivated (maxsize=0) if pytest 运行s:
import sys
from functools import lru_cache
@lru_cache(maxsize=0 if "pytest" in sys.modules else 256)
def fct_parent():
return fct_child()
def fct_child():
return "unmocked"
def test_mock_lru_cache_internal(monkeypatch):
"""This test fails if @lru_cache of fct_parent is active and succeeds otherwise"""
print(f"{fct_parent.cache_info().maxsize=}")
for ii in range(2):
ret_val = f"mocked {ii}"
with monkeypatch.context() as mpc:
mpc.setattr(f"{__name__}.fct_child", lambda: ret_val) # mocks fct_child to return ret_val
assert fct_parent() == ret_val
if __name__ == "__main__":
"""
This module is designed to fail, if called by python
$ python test_lru_cache_mocking.py
and to work if exectued by pytest
$ pytest -s test_lru_cache_mocking.py
The reason is, that the size of the lru_cache is 256 / 0 respectively
and hence test_mock_lru_cache_internal fails / succeeds.
"""
#
from _pytest.monkeypatch import MonkeyPatch
test_mock_lru_cache_internal(MonkeyPatch())
我正在尝试测试一种通过 lru_cache
记住的方法(因为它是一个昂贵的数据库调用)。 pytest-mock
.
代码的简化版本是:
class User:
def __init__(self, file):
# load a file
@lru_cache
def get(self, user_id):
# do expensive call
那我在测试:
class TestUser:
def test_get_is_called(self, mocker):
data = mocker.ANY
user = User(data)
repository.get(user_id)
open_mock = mocker.patch('builtins.open', mocker.mock_open())
open_mock.assert_called_with('/foo')
但我收到以下错误:
TypeError: unhashable type: '_ANY'
发生这种情况是因为 functools.lru_cache
需要存储的键是 可散列的 ,即实现方法 __hash__
或 __cmp__
。
如何在 mocker 中模拟这些方法以使其工作?
我试过了
user.__hash__.return_value = 'foo'
运气不好。
而不是使用 mocker.ANY
(意在断言中用作等于任何对象的占位符的对象)我相信您反而想使用哨兵对象(例如 mocker.sentinel.DATA
).
这似乎在快速测试中有效:
from functools import lru_cache
@lru_cache(maxsize=None)
def f(x):
return (x, x)
def test(mocker):
ret = f(mocker.sentinel.DATA)
assert ret == (mocker.sentinel.DATA, mocker.sentinel.DATA)
对于到达这里试图找出如何测试用 lru_cache
或 alru_cache
装饰的函数的人来说,答案是在每次测试之前
这可以按如下方式完成:
def setup_function():
"""
Avoid the `(a)lru_cache` causing tests with identical parameters to interfere
with one another.
"""
my_cached_function.cache_clear()
如何在 运行ning pytest
时关闭@lru_cache
万一你因为想测试一个 @lru_cache
- 带有不同模拟的修饰函数(但是 lru_cache 阻止你的模拟)而结束了这里......
如果你 运行 pytest!
@lru_cache
的 maxsize
设置为 0
@lru_cache(maxsize=0 if "pytest" in sys.modules else 256)
最小工作示例
with @lru_cache active (maxsize=256) when code 运行s and deactivated (maxsize=0) if pytest 运行s:
import sys
from functools import lru_cache
@lru_cache(maxsize=0 if "pytest" in sys.modules else 256)
def fct_parent():
return fct_child()
def fct_child():
return "unmocked"
def test_mock_lru_cache_internal(monkeypatch):
"""This test fails if @lru_cache of fct_parent is active and succeeds otherwise"""
print(f"{fct_parent.cache_info().maxsize=}")
for ii in range(2):
ret_val = f"mocked {ii}"
with monkeypatch.context() as mpc:
mpc.setattr(f"{__name__}.fct_child", lambda: ret_val) # mocks fct_child to return ret_val
assert fct_parent() == ret_val
if __name__ == "__main__":
"""
This module is designed to fail, if called by python
$ python test_lru_cache_mocking.py
and to work if exectued by pytest
$ pytest -s test_lru_cache_mocking.py
The reason is, that the size of the lru_cache is 256 / 0 respectively
and hence test_mock_lru_cache_internal fails / succeeds.
"""
#
from _pytest.monkeypatch import MonkeyPatch
test_mock_lru_cache_internal(MonkeyPatch())