如何在pytest收集前Mock diskcache.Cache以测试缓存逻辑
解决pytest中Mock diskcache.Cache时机过晚的问题
你的核心问题在于:my_module在导入阶段就已经用真实的diskcache.Cache完成了expensive_function的装饰,而原来的autouse fixture是在测试运行阶段才执行Mock,此时装饰器早已生效,Mock无法覆盖已有的装饰逻辑。
要解决这个问题,需要在pytest测试收集阶段之前完成Mock,确保模块导入时就使用Mock后的Cache类。以下是具体实现方案:
修改后的conftest.py代码
import pytest import unittest.mock import diskcache import importlib import my_module # 替换为你的实际模块名 class MockCache: def __init__(self, *args, **kwargs): pass def memoize(self, fun=None, **kwargs): # 兼容带参数的装饰器写法(如@cache.memoize(expire=3600)) if fun is None: return lambda f: f return fun def pytest_configure(config): if not config.getoption("--cache"): # 在测试收集前替换diskcache.Cache为MockCache mock_cache_patch = unittest.mock.patch.object(diskcache, 'Cache', MockCache) mock_cache_patch.start() # 重新加载模块,确保装饰器使用Mock后的Cache importlib.reload(my_module) # 保存Mock对象,用于后续清理 config._diskcache_mock = mock_cache_patch def pytest_unconfigure(config): # 测试结束后停止Mock,避免污染环境 if hasattr(config, '_diskcache_mock'): config._diskcache_mock.stop() def pytest_addoption(parser): parser.addoption( "--cache", action="store_true", default=False, help=( "Enable caching for tests. Careful: This will disable the test coverage " "of the cached code if the return value is in cache." ), )
关键细节说明
使用pytest_configure钩子
这个钩子在pytest启动后、测试用例收集前执行,是能提前Mock的最早时机,确保模块导入时就使用Mock后的Cache类。重新加载目标模块
若pytest启动时目标模块已被预导入(比如测试文件中提前import了),必须用importlib.reload重新加载模块,让装饰器重新应用Mock后的memoize方法。修正MockCache的memoize实现
真实的diskcache.Cache.memoize是实例方法,且支持带参数的装饰器写法(如expire=3600),因此Mock类的memoize需要处理两种场景:直接装饰函数、先接收参数再返回装饰器。清理Mock资源
通过pytest_unconfigure钩子在测试结束后停止Mock,避免影响后续测试或全局环境。
测试用例示例
# test_my_module.py from my_module import expensive_function from unittest.mock import patch def test_expensive_function_without_cache(): # 验证缓存被禁用时,函数每次调用都会执行真实逻辑 with patch('my_module.expensive_function') as mock_func: mock_func.return_value = "test_result" expensive_function(1) expensive_function(1) assert mock_func.call_count == 2 def test_expensive_function_with_cache(): # 运行时添加--cache参数,验证缓存生效(函数仅执行一次) with patch('my_module.expensive_function') as mock_func: mock_func.return_value = "test_result" expensive_function(1) expensive_function(1) assert mock_func.call_count == 1
内容的提问来源于stack exchange,提问作者Daniel Böckenhoff
相关产品推荐
相关产品推荐

