Python中模拟类内协作对象的最佳实现方式是什么?
在Python中模拟类内协作对象的最佳实践
这是个很好的问题!在Python中模拟类内协作对象,其实有几种主流的方式,我会结合你的代码场景逐一分析,帮你找到最适合的方案:
1. 优先推荐:依赖注入(解耦+灵活测试)
你提到的第一种写法其实就是依赖注入的思路——把协作对象作为参数传给myclass的构造函数,这是我最推荐的方案,因为它从根源上降低了类之间的耦合度。
首先,我们可以稍微优化一下原类,给构造函数加个可选参数,这样不影响正常业务代码的使用,同时方便测试时传入mock:
class myclass: def __init__(self, collaborator=None): # 正常使用时自动创建Coll实例,测试时传入mock self.collaborator = collaborator or Coll() def tested_method(self): val = self.collaborator.val val_2 = self.collaborator.get_val2() # ... 你的业务逻辑
然后测试代码就可以像你写的那样,灵活配置mock的属性和方法返回值:
from unittest import TestCase, mock class TestMainModel(TestCase): def setUp(self): # 创建mock协作对象 self.mock_collab = mock.Mock() self.mock_collab.val = 12 self.mock_collab.get_val2.return_value = 13 # 注入mock到被测试类(sut = System Under Test) self.sut = myclass(collaborator=self.mock_collab) def test_tested_method(self): # 执行被测试方法 result = self.sut.tested_method() # 断言逻辑:比如检查get_val2是否被调用 self.mock_collab.get_val2.assert_called_once() # 断言业务结果符合预期 self.assertEqual(result, 预期结果)
这种方式的好处:
- 让
myclass的依赖关系更清晰,别人看代码一眼就知道它需要一个具备val属性和get_val2方法的协作对象 - 测试时完全控制协作对象的行为,不用担心真实
Coll类的复杂逻辑影响测试 - 以后如果需要替换
Coll的实现(比如换成NewColl),不用修改myclass的代码,符合开闭原则
2. 无需修改原类:使用unittest.mock.patch
如果你因为某些原因不能修改原类的构造函数(比如维护遗留代码),可以用Python标准库的unittest.mock.patch来替换myclass内部引用的Coll类。
注意:这里的关键是patch的路径必须是myclass所在模块中导入Coll的路径,而不是Coll自身定义的模块路径,否则mock不会生效。
示例代码:
from unittest import TestCase, mock # 假设myclass和Coll都在mymodule模块中 from mymodule import myclass, Coll class TestMainModel(TestCase): # patch mymodule模块中的Coll类 @mock.patch('mymodule.Coll') def test_tested_method(self, mock_coll_class): # 获取mock出来的Coll实例(因为myclass会调用Coll()创建实例) mock_coll_instance = mock_coll_class.return_value # 设置实例的属性和方法返回值 mock_coll_instance.val = 12 mock_coll_instance.get_val2.return_value = 13 # 实例化myclass,此时它会使用mock的Coll sut = myclass() # 执行测试 result = sut.tested_method() # 断言Coll类被实例化了一次 mock_coll_class.assert_called_once() # 断言get_val2方法被调用 mock_coll_instance.get_val2.assert_called_once() # 断言业务结果正确 self.assertEqual(result, 预期结果)
这种方式的优势是不用改动生产代码,但缺点是依赖于模块的导入结构,新手容易因为patch路径写错导致mock失效,而且原类的耦合度还是很高。
补充细节:模拟属性的特殊情况
如果Coll的val是一个@property而不是普通属性,直接赋值mock_collab.val = 12可能不会生效,这时候需要用mock.PropertyMock:
# 替换普通属性赋值 mock_collab.val = mock.PropertyMock(return_value=12)
这样mock出来的属性行为会更贴近真实的@property。
总结
- 如果可以修改原类,优先用依赖注入,这是最干净、最可维护的方案
- 如果不能修改原类,再用
mock.patch来临时替换协作类
内容的提问来源于stack exchange,提问作者Alicja Głowacka
相关产品推荐
相关产品推荐

