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

如何创建可调用start()和stop()的patch.object Mock?解决属性错误

问题:如何通过Mixin统一给单元测试批量打patch.object补丁?

我有一套Python单元测试套件(基于Django TestCase),包含20个测试类共300个测试用例,想通过Mixin在setUp中统一应用4个patch.object(禁用耗时操作),避免每个测试类重复写嵌套with语句。

原来用嵌套with的方式可行,但代码繁琐:

from django.test import TestCase
from unittest.mock import patch

class MyTestCase(TestCase):
    def test_stuff(self):
        with patch.object('MyClass1', 'mymethod1') as mock_method_1:
            with patch.object('MyClass2', 'mymethod2') as mock_method_2:
                with patch.object('MyClass3', 'mymethod3') as mock_method_3:
                    with patch.object('MyClass4', 'mymethod4') as mock_method_4:
                        # 执行测试逻辑
                        self.assertEqual(mock_method_4.call_count, 0)

于是我写了个Mixin,在setUp中创建补丁并启动:

class PatchMixin():
    def setUp(self):
        self.mock_method_1 = patch.object(MyClass1, "mymethod1")
        self.mock_method_2 = patch.object(MyClass2, "mymethod2")
        self.mock_method_3 = patch.object(MyClass3, "mymethod3")
        self.mock_method_4 = patch.object(MyClass4, "mymethod4")

        self.mock_method_1.start()
        self.mock_method_2.start()
        self.mock_method_3.start()
        self.mock_method_4.start()

        super().setUp()

    def tearDown(self):
        self.mock_method_1.stop()
        self.mock_method_2.stop()
        self.mock_method_3.stop()
        self.mock_method_4.stop()

        super().tearDown(self)

class MyTestCase(TestCase):
    def test_stuff(self):
        # 执行测试逻辑
        self.assertEqual(self.mock_method_4.call_count, 0)

但运行时报错:

Failed: [undefined]AttributeError: '_patch' object has no attribute 'call_count'

错误原因

你当前的代码中,self.mock_method_4存储的是patch.object()返回的补丁控制器(_patch对象),而非被替换后的mock实例。call_count是mock对象的属性,补丁控制器本身没有这个属性,因此触发报错。

在with语句中,as mock_method_4拿到的是patch.start()返回的mock实例;但你直接将patch.object()的结果存在self中,仅调用了start()却未保存其返回值,这就是问题核心。


解决方案

方案1:保存start()返回的mock实例

修改Mixin,将补丁控制器和mock实例分开存储,确保self中保存的是实际的mock对象:

class PatchMixin():
    def setUp(self):
        # 创建补丁控制器
        self._patch1 = patch.object(MyClass1, "mymethod1")
        self._patch2 = patch.object(MyClass2, "mymethod2")
        self._patch3 = patch.object(MyClass3, "mymethod3")
        self._patch4 = patch.object(MyClass4, "mymethod4")
        
        # 启动补丁,保存返回的mock实例
        self.mock_method_1 = self._patch1.start()
        self.mock_method_2 = self._patch2.start()
        self.mock_method_3 = self._patch3.start()
        self.mock_method_4 = self._patch4.start()
        
        super().setUp()

    def tearDown(self):
        # 通过补丁控制器停止补丁
        self._patch1.stop()
        self._patch2.stop()
        self._patch3.stop()
        self._patch4.stop()
        
        super().tearDown()

测试用例中self.mock_method_4.call_count即可正常访问,因为此时存储的是mock实例。

方案2:用patch.multiple简化批量补丁

如果多个补丁都是针对不同类的方法,使用patch.multiple可以一次性创建并管理多个补丁,代码更简洁:

from unittest.mock import patch, MagicMock

class PatchMixin():
    def setUp(self):
        # patch.multiple同时打多个补丁,spec=True保留原方法签名
        self.mocks = patch.multiple(
            MyClass1, mymethod1=MagicMock(),
            MyClass2, mymethod2=MagicMock(),
            MyClass3, mymethod3=MagicMock(),
            MyClass4, mymethod4=MagicMock(),
            spec=True
        ).start()
        
        super().setUp()

    def tearDown(self):
        patch.stopall()  # 停止所有活跃补丁
        super().tearDown()

测试用例中访问mock的方式:self.mocks['mymethod4'].call_count,无需手动管理每个补丁的启停。

方案3:用装饰器结合Mixin(自动管理补丁生命周期)

借助@patch装饰器给Mixin的setUp方法打补丁,自动处理补丁的启停,无需手动写tearDown:

from unittest.mock import patch

class PatchMixin():
    # 注意装饰器顺序:最后一个装饰器的参数最先传入setUp
    @patch.object(MyClass1, 'mymethod1')
    @patch.object(MyClass2, 'mymethod2')
    @patch.object(MyClass3, 'mymethod3')
    @patch.object(MyClass4, 'mymethod4')
    def setUp(self, mock_method4, mock_method3, mock_method2, mock_method1):
        self.mock_method_1 = mock_method1
        self.mock_method_2 = mock_method2
        self.mock_method_3 = mock_method3
        self.mock_method_4 = mock_method4
        
        super().setUp()

这种方式利用装饰器自动管理补丁的生命周期,适合Django TestCase的执行逻辑(每个测试用例都会调用setUp,装饰器会自动启动和停止补丁)。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 15:27:01