
当函数以 `from module import func` 方式被导入时,需在**使用该函数的模块作用域内**进行 patch(如 `@patch("entry.read_sql")`),而非原始定义模块,否则 mock 不生效。
在 Python 单元测试(尤其是使用 unittest.mock.patch)中,mock 的目标必须是对象在运行时实际被引用的位置,而非其原始定义位置。这是初学者最容易混淆的关键点。
以你的代码为例:
# entry.py
from sql_services import read_sql # ← 此处将 read_sql 绑定为 entry 模块的局部名称
def foo():
df = read_sql("SELECT * FROM schema.table") # ← 实际调用的是 entry.read_sql!
return df虽然 read_sql 定义在 sql_services 中,但 foo() 内部访问的是 entry 模块的全局变量 read_sql —— 这是 Python 导入机制决定的:from ... import 会在当前命名空间创建一个新引用。因此,mock 必须指向 "entry.read_sql",而不是 "sql_services.read_sql"。
✅ 正确的测试写法如下:
立即学习“Python免费学习笔记(深入)”;
# test_entry.py
import pytest
import unittest.mock as mock
import pandas as pd
from entry import foo
@mock.patch("entry.read_sql") # ← 关键:patch 使用方模块中的名称
def test_do_a_read(mock_read_sql):
mock_read_sql.return_value = pd.DataFrame({"id": [1, 2]})
result = foo() # ← 此时调用的是被 mock 的 entry.read_sql
assert len(result) == 2
mock_read_sql.assert_called_once_with("SELECT * FROM schema.table")⚠️ 注意事项:
- ❌ @patch("sql_services.read_sql") 对 foo() 无效,因为 foo 并未通过 sql_services.read_sql 访问该函数;
- ✅ Patch 路径必须与 导入后实际使用的全限定名一致(即 module_name.object_name);
- 若 entry.py 中存在多个导入(如 from sql_services import read_sql, write_sql),需分别 patch entry.read_sql 和 entry.write_sql;
- 使用 pytest 时,也可改用 monkeypatch fixture 实现更灵活的局部 mock(适合复杂场景),但 @patch 仍是标准、清晰的首选。
? 小技巧:可通过 print(entry.read_sql) 或 assert entry.read_sql is not sql_services.read_sql 验证导入是否创建了独立引用,加深理解。
总结:Mock 的本质是“替换运行时符号表中的对象”,不是“替换源码定义”。始终 patch 被测试代码所在模块中该符号的名称,即可避免因导入方式不同导致的 mock 失效问题。










