pytest-mock 可正常模拟私有方法,关键在于 patch 路径必须匹配调用处的引用位置而非定义位置,且需注意内联调用不可 mock、断言要包含 self、避免混淆测试与 mock 私有方法。

pytest-mock 本身不“无法模拟私有方法”——问题出在你试图 mock 的对象根本没被调用,或者你 mock 的路径错了。私有方法(_xxx)和公有方法在 mock 层面没有任何区别,只要路径对、作用域对、调用链对,就能 mock。
mock.patch 路径必须指向被调用处的引用位置
不是定义位置,而是导入/使用位置。这是最常踩的坑。
- 如果
module_a.py定义了class Service:和def _fetch_data(self): ...,而module_b.py中写了from module_a import Service,再在某函数里调用svc._fetch_data(),那你必须 patch"module_b.Service._fetch_data",而不是"module_a.Service._fetch_data" - 错误示例:
mocker.patch("module_a.Service._fetch_data")→ 即使定义在那里,但module_b是从自己的命名空间里访问的,patch 错了就等于没打上 - 验证方式:在测试里加一句
print(Service._fetch_data),看它是不是;如果不是,说明 patch 失败
私有方法被内联调用时不能 patch
如果私有方法只在类内部被其他方法调用(比如 public_method 里直接写 self._helper()),那 patch 它没有意义——你该 mock 的是它的上游调用点,或改用实例方法注入。
- 不要写:
mocker.patch("mylib.MyClass._helper")然后期望obj.public_method()触发它被调用 —— 这个 patch 不会生效,因为 Python 解析的是self._helper,而 self 是真实实例,不是模块级引用 - 正确做法:要么 patch
public_method本身;要么在测试中构造实例后,直接替换实例属性:obj._helper = mocker.Mock(return_value="ok") - 注意:后者只对实例方法有效,对
@staticmethod或@classmethod的私有方法需 patch 类路径(如"mylib.MyClass._static_helper")
断言时别漏掉 self 参数
实例方法的 mock 调用记录里,self 是第一个隐式参数,assert_called_with() 默认会校验它。
- 错误写法:
mock_helper.assert_called_with("arg1")→ 实际调用是mock_helper(,断言失败, "arg1") - 正确写法:
mock_helper.assert_called_with(mocker.ANY, "arg1"),或更明确地:mock_helper.assert_called_once()(不校验参数) - 如果想校验参数又不想管
self,用assert_called_with(unittest.mock.ANY, "arg1"),但注意mocker.ANY来自 pytest-mock,不是 unittest.mock
别混淆“测试私有方法”和“mock 私有方法”
这两件事完全无关。前者是你直接调用 obj._helper() 做单元测试;后者是你在测别的逻辑时,把 _helper 替换成 mock 避免副作用。很多人卡在这里,是因为误以为“私有 = 不能 mock”,其实只是路径和调用方式没理清。
- 如果你只是想验证
_helper的行为,直接调用就行:assert obj._helper(42) == "done"—— 不需要pytest-mock - 只有当你测
public_method且想隔离_helper的 I/O、计算或外部依赖时,才需要 mock 它 - mock 后忘记设
return_value或side_effect,会导致后续逻辑抛None相关异常,这不是私有方法的问题,是 mock 配置不全
真正容易被忽略的,是 patch 路径和调用上下文之间的映射关系——它不取决于代码写在哪,而取决于 Python 解释器在运行时从哪找到那个名字。写错一层包名、少一个点、拼错模块名,mock 就静默失效,测试还通过,隐患更大。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











