You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python单元测试中Mock cachetools缓存值?自定义装饰器Mock失败排查

如何在Python单元测试中Mock cachetools缓存值(解决自定义装饰器Mock失败问题)

先直接说你的测试失败的核心原因,再给你两种可行的解决方案:

问题根源

你的测试代码犯了两个关键错误:

  1. Mock对象的用法错误:total_cache是一个已经初始化好的TTLCache实例,不是可调用的函数/类,所以设置mock_total_cache.return_value = self.fake_cache完全无效——return_value只对可调用对象(比如类、函数)生效,对于已存在的实例,这个操作不会改变它的任何行为。
  2. 装饰器的绑定时机问题:cache_controller装饰器在ExampleClass定义时,就已经把原total_cache实例的引用绑定到了装饰器内部的闭包中。哪怕你后来Mock了src.run.total_cache,装饰器里用的还是原来那个空的TTLCache实例。

解决方案一:重新绑定装饰器的缓存实例

既然装饰器已经绑定了原缓存,我们可以直接替换测试类中方法使用的缓存,重新给目标方法应用装饰器并传入假缓存:

import unittest
from cachetools.ttl import TTLCache
from src.run import ExampleClass
from unittest import TestCase
from src.wrappers.cache_controller import cache_controller

class TestCacheController(TestCase):
    def setUp(self):
        self.fake_cache = TTLCache(maxsize=10, ttl=3600)
        self.fake_cache[(5, 6)] = 1  # 直接给假缓存设置预期值
        self._class = ExampleClass()
        # 拿到原函数(__wrapped__是functools.wraps保留的原函数引用)
        original_method = self._class.example_method.__wrapped__
        # 重新用假缓存应用装饰器,替换掉原来的方法
        self._class.example_method = cache_controller(
            cache=self.fake_cache, cache_args=("a", "b")
        )(original_method)

    def tearDown(self):
        self._class = None

    def test_if_cache_filled(self):
        result = self._class.example_method(a=5, b=6)
        cache_expected = 1
        print("Result:", result)
        self.assertEqual(result, cache_expected)

解决方案二:直接Mock缓存实例的方法

如果你不想修改装饰器的绑定逻辑,可以直接Mock原缓存实例的get和update方法,控制它们的返回值和行为:

import unittest
from src.run import ExampleClass
from unittest import TestCase, mock

class TestCacheController(TestCase):
    def setUp(self):
        self._class = ExampleClass()

    @mock.patch("src.run.total_cache")
    def test_if_cache_filled(self, mock_total_cache):
        # 设置get方法返回我们预期的缓存值
        mock_total_cache.get.return_value = 1
        result = self._class.example_method(a=5, b=6)
        cache_expected = 1
        print("Result:", result)
        self.assertEqual(result, cache_expected)
        
        # 还可以验证缓存方法的调用是否符合预期
        mock_total_cache.get.assert_called_once_with((5, 6))
        # 因为缓存命中,update方法不应该被调用
        mock_total_cache.update.assert_not_called()

额外优化建议

如果后续需要频繁测试这个装饰器,建议把缓存的注入方式改得更灵活,比如让类支持传入自定义缓存:

# 修改src/run.py
from .wrappers.cache_controller import cache_controller
from .helpers.cache import total_cache

class ExampleClass(object):
    def __init__(self, cache=None):
        # 优先使用传入的缓存,没有则用默认的total_cache
        self._cache = cache or total_cache

    # 修改装饰器,让它从实例中获取缓存
    @cache_controller(
        cache=lambda self: self._cache, cache_args=("a", "b")
    )
    def example_method(self, a: int, b: int):
        return a+b

当然这需要你调整cache_controller的逻辑,让它支持接收可调用对象来获取缓存实例,这样测试时直接传入假缓存会更简洁。

内容的提问来源于stack exchange,提问作者sumeyyeemir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:51:02