如何为测试函数内创建的实例方法添加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()
方案说明
- 类级别patch整个
MyClass,所有测试方法都会自动继承这个patch,无需重复编写。 - 通过
mock_my_class.return_value获取do_data_calculations中创建的实例对象(因为MyClass()调用会返回mock实例)。 - 针对每个测试用例,可自定义不同的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
相关产品推荐
相关产品推荐

