为何仅Mock类方法而非整个类时,我的单元测试仍能正常运行?
我之前以为,只有当被测代码没有实例化类的时候,才能Mock这个类的方法;如果已经实例化了类,就必须Mock整个类(就像处理内置对象那样)。但今天“误操作”后发现测试居然正常运行,所以搞不懂什么时候该Mock整个类,什么时候Mock类方法就行。
代码示例
Engine.py
class Engine(object): def __init__(self, start_temperature): self._started = False self._temperature = start_temperature def start(self): self._started = True self._set_temperature(10) def stop(self): self._started = False self._set_temperature(-10) def get_temperature(self): return self._temperature def _set_temperature(self, value): self._temperature += value
MyClass.py
from Engine import Engine class MyClass(object): def __init__(self): self.engine = Engine(0) def check_start(self): self.engine.start() def check_stop(self): self.engine2 = Engine(1) self.engine2.stop() def get_temperature(self): return self.engine.get_temperature()
test_MyClass.py
import unittest import unittest.mock as mock import MyClass class TestMyClass(unittest.TestCase): def test__initiation_returns_0_temperature(self): c = MyClass.MyClass() temperature = c.get_temperature() self.assertEqual(temperature, 0) @mock.patch("MyClass.Engine") def test_engine_get_temperature_mocked_via_class(self, mock_Engine_cls): mock_Engine_cls.return_value.get_temperature.return_value = 11 c = MyClass.MyClass() c.check_start() temperature = c.get_temperature() self.assertEqual(temperature, 11) @mock.patch("MyClass.Engine.get_temperature") def test_engine_get_temperature_mocked_via_method(self, get_temperature_mock): get_temperature_mock.return_value = 12 c = MyClass.MyClass() c.check_stop() temperature = c.get_temperature() self.assertEqual(temperature, 12) if __name__ == "__main__": unittest.main()
我原本以为第三个测试会报错,因为过去实例化类时若不Mock整个类就会出错,但现在它通过了。这是为什么?
原因解析
你第三个测试能运行通,核心是Python的类方法绑定机制和unittest.mock的工作逻辑:
类方法属于类的属性
Engine.get_temperature是类Engine的一个属性(本质是函数对象)。当你用@mock.patch("MyClass.Engine.get_temperature")替换它时,你修改的是类层面的方法定义。实例调用方法的查找规则
当MyClass实例化Engine后,调用self.engine.get_temperature()时,Python会先在实例自身的属性里找这个方法;如果找不到,就会去对应的类属性里找。因为你已经把类上的get_temperature换成了Mock对象,所以实例调用的就是这个Mock方法,自然能返回你预设的12。两种Mock方式的区别
- Mock整个类:直接替换掉类的定义,所有实例化出来的都是Mock对象,你可以完全控制实例的所有方法、属性,适合需要屏蔽真实类初始化逻辑,或同时Mock多个方法的场景。
- Mock单个类方法:只替换类上的特定方法,实例仍然是真实的
Engine对象,其他方法还是走真实逻辑,适合只需要修改单个方法行为的轻量场景。
为什么之前你觉得会报错?
大概率是之前遇到的场景有差异:比如实例自己重写了目标方法,或者Mock的路径不对(比如Mock了Engine.Engine.get_temperature而不是MyClass.Engine.get_temperature)。但这个例子里,MyClass是从Engine导入类,所以MockMyClass.Engine.get_temperature是正确的路径,能覆盖所有实例对该方法的调用。
什么时候用哪种Mock方式?
- 用Mock整个类:当你需要跳过真实类的初始化逻辑,或者需要同时控制多个方法/属性的行为时。
- 用Mock单个类方法:当你只需要修改某一个方法的行为,其他方法的真实逻辑不影响测试结果时。
内容的提问来源于stack exchange,提问作者Nemelis

