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

如何为测试函数内创建的实例方法添加mock副作用?

问题描述

现有以下不可修改的代码:

MyClass.py

class MyClass:
        def __init__(self):
            self.fetched_data = False
    
        def get_data(self):
            self.fetched_data = True
            return [1, 2, 3]

function_to_test.py

from MyClass import MyClass

def do_data_calculations():
    my_class = MyClass()
    data = my_class.get_data()
    print(data)
    print(f"Data fetched: {my_class.fetched_data}")
    return True

编写单元测试时,需要mock get_data 方法,但使用类级别@patch时出现错误:

测试代码

from function_to_test import do_data_calculations
import unittest
from unittest.mock import patch

def get_data_side_effect(self):
    self.fetched_data = True
    return [10, 9, 8]

@patch('function_to_test.MyClass.get_data')
class TestMyClass(unittest.TestCase):
    def test_greet(self, mock_get_data):
        mock_get_data.side_effect = get_data_side_effect
        do_data_calculations()
        self.assertTrue(mock_get_data.called)

if __name__ == "__main__":
    unittest.main()

错误信息

Traceback (most recent call last):
  File "C:\Users\user4219\AppData\Local\miniconda3\envs\azure\lib\unittest\mock.py", line 1379, in patched
    return func(*newargs, **newkeywargs)
  File "c:\Users\user4219\source\projects\mockingtest\test_function.py", line 14, in test_greet
    do_data_calculations()
  File "c:\Users\user4219\source\projects\mockingtest\function_to_test.py", line 5, in do_data_calculations
    data = my_class.get_data()
  File "C:\Users\user4219\AppData\Local\miniconda3\envs\azure\lib\unittest\mock.py", line 1114, in call
    return self._mock_call(*args, **kwargs)
  File "C:\Users\user4219\AppData\Local\miniconda3\envs\azure\lib\unittest\mock.py", line 1118, in _mock_call
    return self._execute_mock_call(*args, **kwargs)
  File "C:\Users\user4219\AppData\Local\miniconda3\envs\azure\lib\unittest\mock.py", line 1179, in _execute_mock_call
    result = effect(*args, **kwargs)
TypeError: get_data_side_effect() missing 1 required positional argument: 'self'

需求:使用类级别patch(避免每个测试重复patch),且每个测试可设置不同的side effect,同时能正确修改实例的fetched_data属性。

错误原因

当用@patch('function_to_test.MyClass.get_data')替换类的方法时,实例访问到的get_data是一个普通的Mock对象,而非Python的绑定方法。调用my_class.get_data()时不会自动传入实例本身(self),导致side_effect函数因缺少参数报错。

解决方案

改为patch整个MyClass类,在测试中获取mock的实例对象,直接给实例的get_data方法设置side_effect,这样可以直接操作实例属性:

from function_to_test import do_data_calculations
import unittest
from unittest.mock import patch

@patch('function_to_test.MyClass')
class TestMyClass(unittest.TestCase):
    def test_greet(self, mock_my_class):
        # 获取mock类创建的实例对象
        mock_instance = mock_my_class.return_value
        
        # 定义side_effect,直接操作mock实例的属性
        def get_data_side_effect():
            mock_instance.fetched_data = True
            return [10, 9, 8]
        
        # 给实例的get_data方法设置side_effect
        mock_instance.get_data.side_effect = get_data_side_effect
        
        do_data_calculations()
        
        # 验证方法被调用,且属性被正确修改
        self.assertTrue(mock_instance.get_data.called)
        self.assertEqual(mock_instance.fetched_data, True)

    def test_another_case(self, mock_my_class):
        # 另一个测试用例,设置不同的side_effect
        mock_instance = mock_my_class.return_value
        
        def get_data_side_effect():
            mock_instance.fetched_data = False
            return [5, 6, 7]
        
        mock_instance.get_data.side_effect = get_data_side_effect
        
        do_data_calculations()
        
        self.assertTrue(mock_instance.get_data.called)
        self.assertEqual(mock_instance.fetched_data, False)

if __name__ == "__main__":
    unittest.main()

方案说明

  1. 类级别patch整个MyClass,所有测试方法都会自动继承这个patch,无需重复编写。
  2. 通过mock_my_class.return_value获取do_data_calculations中创建的实例对象(因为MyClass()调用会返回mock实例)。
  3. 针对每个测试用例,可自定义不同的side_effect函数,直接修改mock实例的属性,满足不同测试场景的需求。
补充方案(若坚持patch类方法)

如果一定要patch类的get_data方法,可通过mock_get_data的call_args获取实例,但这种方式不够直观:

from function_to_test import do_data_calculations
import unittest
from unittest.mock import patch

@patch('function_to_test.MyClass.get_data')
class TestMyClass(unittest.TestCase):
    def test_greet(self, mock_get_data):
        def get_data_side_effect():
            # 从mock的调用参数中获取实例
            instance = mock_get_data.call_args[0][0]
            instance.fetched_data = True
            return [10, 9, 8]
        
        mock_get_data.side_effect = get_data_side_effect
        do_data_calculations()
        
        self.assertTrue(mock_get_data.called)
        # 验证实例属性被修改
        instance = mock_get_data.call_args[0][0]
        self.assertEqual(instance.fetched_data, True)

注意:这种方式依赖mock的调用顺序,若get_data被多次调用,需要处理call_args_list,不如第一种方案可靠。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 06:09:50