测试类__init__中依赖类的最佳Mock方法咨询
Python Mock Generator依赖类的方案点评与最佳实践
场景回顾
待测试的Generator类在__init__中实例化了Foo、Bar、Baz三个依赖类,这些类的方法会调用MongoDB,测试时需Mock这些依赖以避免连接真实数据库。
现有方案点评
方案1:unittest.patch直接Mock依赖类
class TestGenerator(unittest.TestCase): @patch('generatorModule.Foo') @patch('generatorModule.Bar') @patch('generatorModule.Baz') def setUpClass(cls, patchFoo, patchBar, patchBaz): cls.foo = patchFoo.return_value cls.foo.get_foo.return_value.get_value.return_value = 'Some string I want to return' cls.bar = patchBar.return_value cls.bar.get_bar.return_value.get_value.return_value = 'Another string I want to return' cls.baz = patchBaz.return_value cls.baz.get_baz.return_value.get_value.return_value = 'Another another string I want to return' cls.generator = Generator() ...rest of test...
- 优点:遵循Mock的核心原则——替换被测试代码实际导入的依赖类,属于正统Mock方式,不会破坏
Generator原有的__init__逻辑,后续维护时不容易出现意外问题。 - 缺点:多层链式设置
return_value确实繁琐,调用链越长,代码可读性和维护性越差。
方案2:Mock Generator的__init__后手动赋值属性
class TestGenerator(unittest.TestCase): @patch.object(GeneratClass, "__init__", Mock(return_value=None)) def setUpClass(cls): mockFoo = MagicMock() mockFoo.get_foo.return_value = 'Some string I want to return' mockBar = MagicMock() mockBar.get_bar.return_value = 'Another string I want to return' mockBaz = MagicMock() mockBaz.get_baz.return_value = 'Another another string I want to return' cls.generator = Generator() cls.generator.foo = mockFoo cls.generator.bar = mockBar cls.generator.baz = mockBaz ...rest of test...
- 优点:手动设置Mock属性的方式更直观,避免了多层链式调用的繁琐。
- 缺点:直接Mock
__init__会完全跳过原构造逻辑,存在潜在风险:- 后续
Generator的__init__新增初始化逻辑(比如参数校验、其他属性绑定)时,测试代码会完全忽略这些逻辑,导致测试与实际代码脱节。 - 硬编码属性赋值的方式,一旦
Generator内部对foo/bar/baz的命名或引用方式改变,测试代码会直接报错,维护成本更高。
- 后续
最佳方案优化
基于方案1的正统性,优化链式调用的繁琐问题,同时规避方案2的风险,以下是两种可行的优化方式:
方式1:拆分链式调用,用Mock对象嵌套替代
把多层调用链拆分成独立的Mock对象,提升代码可读性:
from unittest.mock import patch, MagicMock class TestGenerator(unittest.TestCase): @patch('generatorModule.Foo') @patch('generatorModule.Bar') @patch('generatorModule.Baz') def setUpClass(cls, patchFoo, patchBar, patchBaz): # Mock Foo的调用链 foo_value_mock = MagicMock() foo_value_mock.get_value.return_value = 'Some string I want to return' cls.foo = patchFoo.return_value cls.foo.get_foo.return_value = foo_value_mock # Mock Bar的调用链 bar_value_mock = MagicMock() bar_value_mock.get_value.return_value = 'Another string I want to return' cls.bar = patchBar.return_value cls.bar.get_bar.return_value = bar_value_mock # Mock Baz的调用链 baz_value_mock = MagicMock() baz_value_mock.get_value.return_value = 'Another another string I want to return' cls.baz = patchBaz.return_value cls.baz.get_baz.return_value = baz_value_mock cls.generator = Generator()
方式2:封装工具函数简化链式赋值
如果这类多层Mock场景较多,可以封装一个工具函数统一处理:
from unittest.mock import patch, MagicMock def mock_call_chain(root_mock, call_chain, return_value): """简化多层return_value设置""" current_mock = root_mock # 遍历调用链的前n-1个节点 for attr in call_chain[:-1]: current_mock = getattr(current_mock, attr).return_value # 设置最后一个节点的返回值 getattr(current_mock, call_chain[-1]).return_value = return_value class TestGenerator(unittest.TestCase): @patch('generatorModule.Foo') @patch('generatorModule.Bar') @patch('generatorModule.Baz') def setUpClass(cls, patchFoo, patchBar, patchBaz): cls.foo = patchFoo.return_value mock_call_chain(cls.foo, ['get_foo', 'get_value'], 'Some string I want to return') cls.bar = patchBar.return_value mock_call_chain(cls.bar, ['get_bar', 'get_value'], 'Another string I want to return') cls.baz = patchBaz.return_value mock_call_chain(cls.baz, ['get_baz', 'get_value'], 'Another another string I want to return') cls.generator = Generator()
长期优化建议:改为依赖注入模式
如果可以调整Generator的设计,建议改为依赖注入模式,彻底消除Mock类的需求:
# 优化后的Generator类 class Generator: def __init__(self, foo=None, bar=None, baz=None): # 允许外部传入依赖实例,默认使用真实类 self.foo = foo or Foo() self.bar = bar or Bar() self.baz = baz or Baz() # 测试时直接传入Mock实例 class TestGenerator(unittest.TestCase): def setUpClass(cls): mockFoo = MagicMock() mock_call_chain(mockFoo, ['get_foo', 'get_value'], 'Some string I want to return') mockBar = MagicMock() mock_call_chain(mockBar, ['get_bar', 'get_value'], 'Another string I want to return') mockBaz = MagicMock() mock_call_chain(mockBaz, ['get_baz', 'get_value'], 'Another another string I want to return') cls.generator = Generator(foo=mockFoo, bar=mockBar, baz=mockBaz)
这种方式让代码的可测试性大幅提升,测试逻辑也更清晰。
内容的提问来源于stack exchange,提问作者Collecto
相关产品推荐
相关产品推荐

