17370845950

如何在 pytest 中跳过 joblib 缓存并直接调用原始函数

在使用 pytest 测试被 `joblib.memory.cache` 装饰的函数时,可通过访问其 `.func` 属性绕过缓存,直接执行原始逻辑,确保测试真实、可重复且不依赖磁盘缓存状态。

joblib.Memory.cache 装饰器会将原函数封装为一个 MemorizedFunc 实例,该实例不仅支持缓存调用,还保留了对原始函数的引用——即 .func 属性。这是 joblib 官方提供的标准方式,无需引入 mocking(如 unittest.mock.patch 或 pytest-mock),既简洁又可靠。

例如,以下代码定义了一个带缓存的平方函数:

from joblib import Memory

memory = Memory(location="cache")

@memory.cache
def func(a):
    print("Executing original function!")  # 可用于验证是否真正执行
    return a ** 2

在单元测试中,若直接调用 func(2),joblib 可能从磁盘读取缓存结果(甚至首次运行后生成缓存文件),导致无法验证函数逻辑本身,也使测试耦合于外部状态。此时应改用:

def test_func():
    assert func.func(2) == 4  # ✅ 直接调用原始函数,完全跳过缓存

✅ 优势说明:

  • 零副作用:不创建/读取缓存目录,无需清理 location;
  • 高可靠性:不依赖 mock 行为,避免因 patch 错误目标(如误 mock Memory.__init__ 或 cache 方法)导致测试失效;
  • 语义清晰:.func 是 joblib 公开 API,文档明确支持,长期兼容性好。

⚠️ 注意事项:

  • 不要尝试 mock Memory 实例或重置 memory 对象(如 memory.clear()),这会影响其他测试的隔离性,且无法保证当前调用一定“不命中缓存”;
  • 若需测试缓存行为本身(如验证是否命中/未命中),应另起测试用例,并配合 tmp_path 提供临时 cache 目录 + 显式清理;
  • 确保被测函数未被多次装饰(如叠加 @lru_cache 或自定义装饰器),否则 .func 链可能断裂——此时建议重构为显式分离逻辑与缓存(如将核心逻辑提取为无装饰函数,再由缓存版本调用)。

总结:对绝大多数单元测试场景,cached_func.func(...) 是跳过 joblib 缓存最直接、最健壮的方式。它让测试回归本质——验证业务逻辑,而非缓存机制。