使用 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_cachealru_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_cachemaxsize 设置为 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())