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

使用Pytest测试带@cache装饰器的类时Mock失效问题

问题:带@cache装饰器的类无法被Mock替换方法

我在用Pytest给带缓存的类写单元测试时遇到了问题:尝试用mockfunc替换类A里的slowfunc,但只有去掉@cache装饰器时Mock才生效。

原测试代码

import pytest
from functools import cache
from unittest.mock import patch

@cache
class A:
    def __init__(self):
        print("calling init")
        
    def slowfunc(self, text):
        print("calling slowfunc:", text)
        return False
        
    def call(self):
        return self.slowfunc("blah")


class B:
    def call(self):
        a = A()
        return a.call()
            

class Test_B:
    def mockfunc(self, text):
        print("calling mockfunc:", text)
        return True
    
    @patch.object(A, 'slowfunc', mockfunc)
    def test_B(self):
        b = B()
        result = b.call()
        assert result == True
        

if __name__ == "__main__":
    retcode = pytest.main(["-xs", __file__])

两种运行结果

注释掉@cache时(符合预期)

example.py calling init
calling mockfunc: blah
.

=== 1 passed in 0.07s ===

启用@cache时(测试失败)

example.py calling init
calling slowfunc: blah
F

=== FAILURES ===
___ Test_B.test_B ___

self = <example.Test_B object at 0x7f305eaaa1f0>

    @patch.object(A, 'slowfunc', mockfunc)
    def test_B(self):
        b = B()
        result = b.call()
>       assert result == True
E       assert False == True

example.py:27: AssertionError
=== short test summary info ===
FAILED example.py::Test_B::test_B - assert False == True
!!!! stopping after 1 failures !!!
=== 1 failed in 0.23s ===

启用@cache后,代码依然调用原slowfunc导致测试失败,就算在测试开始时调用A.cache_clear()也无法解决问题,这是为什么?


原因分析与解决办法

核心原因

@cache装饰类时,本质是把类的实例化过程包装成了一个缓存函数。此时你看到的A已经不是原来的类,而是这个缓存包装函数。

当你用@patch.object(A, 'slowfunc')时,修改的是这个包装函数的属性,而不是实际类的方法。第一次创建A()实例时,缓存会把这个实例存起来,后续调用A()直接返回缓存实例——而这个实例的slowfunc是创建时的原方法,根本没被Mock影响。就算调用A.cache_clear(),也只是清空缓存,但你Mock的是包装函数,新创建的实例依然会用原类的方法。

解决办法

推荐两种实用方案:

1. 先清缓存+上下文管理器Mock

调整测试逻辑,先清空缓存,再用with块临时Mock类方法,确保新创建的实例能用上Mock:

class Test_B:
    def mockfunc(self, text):
        print("calling mockfunc:", text)
        return True
    
    def test_B(self):
        # 清空缓存,确保后续创建新实例
        A.cache_clear()
        # 用上下文管理器临时替换A的slowfunc
        with patch.object(A, 'slowfunc', self.mockfunc):
            b = B()
            result = b.call()
            assert result == True

这样做的好处是:测试结束后Mock自动还原,不会污染其他测试用例,逻辑也清晰。

2. 换实例缓存的实现方式(长期方案)

把类上的@cache换成类方法实现的实例缓存,让原类保持正常结构,Mock就能正常工作:

class A:
    def __init__(self):
        print("calling init")
        
    def slowfunc(self, text):
        print("calling slowfunc:", text)
        return False
        
    def call(self):
        return self.slowfunc("blah")

# 用类方法实现单例缓存
class AWithCache:
    _cached_instance = None
    @classmethod
    def get_instance(cls):
        if cls._cached_instance is None:
            cls._cached_instance = cls()
        return cls._cached_instance

# 修改B类的调用逻辑
class B:
    def call(self):
        a = AWithCache.get_instance()
        return a.call()

这种方式下,你可以直接MockA.slowfunc,或者MockAWithCache.get_instance返回一个Mock实例,灵活性更高。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 13:35:25