You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何仅Mock类方法而非整个类时,我的单元测试仍能正常运行?

关于Python 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的工作逻辑:

  1. 类方法属于类的属性
    Engine.get_temperature是类Engine的一个属性(本质是函数对象)。当你用@mock.patch("MyClass.Engine.get_temperature")替换它时,你修改的是类层面的方法定义。

  2. 实例调用方法的查找规则
    当MyClass实例化Engine后,调用self.engine.get_temperature()时,Python会先在实例自身的属性里找这个方法;如果找不到,就会去对应的类属性里找。因为你已经把类上的get_temperature换成了Mock对象,所以实例调用的就是这个Mock方法,自然能返回你预设的12。

  3. 两种Mock方式的区别

    • Mock整个类:直接替换掉类的定义,所有实例化出来的都是Mock对象,你可以完全控制实例的所有方法、属性,适合需要屏蔽真实类初始化逻辑,或同时Mock多个方法的场景。
    • Mock单个类方法:只替换类上的特定方法,实例仍然是真实的Engine对象,其他方法还是走真实逻辑,适合只需要修改单个方法行为的轻量场景。
  4. 为什么之前你觉得会报错?
    大概率是之前遇到的场景有差异:比如实例自己重写了目标方法,或者Mock的路径不对(比如Mock了Engine.Engine.get_temperature而不是MyClass.Engine.get_temperature)。但这个例子里,MyClass是从Engine导入类,所以MockMyClass.Engine.get_temperature是正确的路径,能覆盖所有实例对该方法的调用。

什么时候用哪种Mock方式?

  • 用Mock整个类:当你需要跳过真实类的初始化逻辑,或者需要同时控制多个方法/属性的行为时。
  • 用Mock单个类方法:当你只需要修改某一个方法的行为,其他方法的真实逻辑不影响测试结果时。

内容的提问来源于stack exchange,提问作者Nemelis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 02:24:53