Python mock技术:patch与属性模拟
理解mock与patch的替换机制
在Python单元测试中,unittest.mock模块提供了强大的隔离能力。Mock类本身是一个可灵活配置行为的占位对象,能自动记录调用历史并拦截任意属性访问;而patch则是临时替换目标对象的上下文管理器或装饰器。理解两者的核心区别在于:Mock负责扮演,patch负责替换。使用patch时必须遵循替换被测试代码实际查找的对象这一铁律。例如,若业务模块A通过from B import func导入了外部函数,测试A时应使用patch('A.func')而非patch('B.func')。因为Python在运行时会先在A的命名空间中查找func,直接替换B模块的原始定义无法影响已绑定到A的引用。建立这种基于查找路径的模拟思维,是避免测试失效与依赖泄漏的第一步。
使用patch模拟函数、类与返回值
patch支持装饰器与上下文管理器两种形态,可无缝嵌入测试用例。通过@patch('模块.函数')或with patch('模块.函数') as mock_func:,可在测试期间精准拦截外部依赖。控制返回值最常用return_value属性,例如设置mock_func.return_value = 42可使所有调用直接返回该值。若需模拟异常或序列行为,应使用side_effect:将其赋值为异常类可在调用时抛出指定错误,赋值为可迭代对象则按顺序返回元素。模拟类时,patch会替换类本身,其return_value即为实例化后的Mock对象。例如@patch('db.Session')后,通过mock_session.return_value.query.all.return_value = []可精准控制ORM查询结果,从而在无真实数据库环境下验证业务逻辑分支。

模拟对象属性与配置行为
当依赖对象包含只读属性、property装饰器方法或需动态配置实例状态时,直接对Mock对象赋值可能触发AttributeError或行为异常。此时应使用patch.object或PropertyMock。patch.object(target, 'attr_name', new=mock_obj)可直接替换目标对象的特定属性,适用于实例成员或模块级变量。对于property,需结合PropertyMock:先创建mock_prop = PropertyMock(return_value='test'),再通过patch.object(cls, 'prop_name', mock_prop)挂载。需注意,PropertyMock仅拦截属性访问,不改变底层数据结构,且必须配合patch.object使用。此外,对于C扩展模块或内置类型的只读属性,纯Python层面的patch可能失效,此时需考虑重构代码引入依赖注入,或改用更底层的拦截方案。
验证调用并排查patch常见陷阱
模拟完成后,必须通过断言验证交互是否符合预期。mock_obj.assert_called_once_with(arg1, arg2)可精确校验调用次数与参数;assert_called()、assert_any_call()等则适用于宽松验证。排查patch陷阱时,首要检查路径是否正确:若测试报AttributeError,通常是因为路径指向了定义处而非使用处。其次,装饰器顺序遵循自下而上原则,最靠近函数的patch会最先注入参数,顺序颠倒会导致Mock对象错位。最后,避免在模块加载期过早patch,应确保替换发生在测试用例执行的生命周期内。若断言失败,可打印mock_obj.call_args_list与mock_obj.mock_calls,对比实际调用轨迹与预期差异,快速定位逻辑漏洞或参数传递错误。

本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













