直接 mock sqlite3.connect 会失效,因为 mock 必须精准匹配被测代码中实际 import 和调用的路径,如 db.get_db 而非底层 sqlite3.connect;需按导入位置、作用域和相对导入规则正确 patch,并用 magicmock 构造可迭代、支持 fetchall 等行为的假 cursor。

为什么直接 mock sqlite3.connect 会失效?
很多人一上来就写 patch('sqlite3.connect'),结果测试里还是连了真实数据库——因为实际代码里可能用的是 sqlalchemy.create_engine、pymysql.connect,甚至封装过的 DatabaseManager.get_connection()。mock 对象必须精准匹配「被调用时实际 import 和使用的路径」,而不是你以为的“底层模块”。
常见错误现象:AssertionError: Expected 'connect' to be called once. Called 0 times.
- 检查被测代码中 import 的位置:如果代码写的是
from db import get_db,就要 patch'db.get_db',不是'sqlite3.connect' - 确认 patch 作用域:在类方法上 patch,要写成
@patch('db.get_db');在函数内 patch,要用with patch('db.get_db') as mock_conn: - 注意相对导入:包内模块引用时,patch 路径以“测试运行时的入口模块”为根,不是文件物理路径
如何让 mock 返回可查询的假结果?
只 mock 连接对象不够,还得让它返回能执行 .execute()、支持 .fetchall() 的 cursor。硬编码返回值容易漏字段或类型不匹配,建议用 Mock 配合 return_value 层层构造。
使用场景:验证业务逻辑是否正确处理了查询结果,比如用户是否存在、余额是否足够。
- 模拟单行结果:
mock_cursor.fetchall.return_value = [('alice', 100)] - 模拟空结果:
mock_cursor.fetchall.return_value = [] - 让 execute 返回不同结果(多次调用):
mock_cursor.execute.side_effect = [None, None]或用side_effect返回不同数据 - 避免忘记设置
rowcount或lastrowid:插入后逻辑依赖这些属性时,需显式赋值,如mock_cursor.lastrowid = 123
用 unittest.mock.MagicMock 还是 Mock?
Mock 是基础类,行为严格;MagicMock 预置了常见魔术方法(__len__、__iter__、__getitem__),对数据库 cursor/row 这种常被迭代或下标访问的对象更友好。
性能影响不大,但兼容性差异明显:
- 如果测试中写了
for row in cursor:,用Mock会报TypeError: 'Mock' object is not iterable;换成MagicMock就能过 - 如果 mock 的是 SQLAlchemy 的
Result对象,它内部调用__bool__判断是否为空,MagicMock默认返回True,需手动设mock_result.__bool__.return_value = False - 不要无脑全用
MagicMock:它会响应所有未定义方法调用,掩盖真实缺失逻辑,调试时反而难定位问题
SQLAlchemy 场景下该 patch 哪一层?
SQLAlchemy 抽象程度高,mock 点选错会导致 session 没生效、事务没回滚、或 ORM 映射失败。最稳的方式是 patch 你代码里「真正拿到 session 的地方」,而不是深挖到 Engine 或 Connection。
例如:
def get_user(user_id):
with get_db_session() as session: # ← 这里是关键入口
return session.query(User).filter(User.id == user_id).first()
就该 patch 'module_name.get_db_session',让它返回一个带 mock query 的 session。
- 推荐构造方式:
mock_session = MagicMock()+mock_session.query.return_value.filter.return_value.first.return_value = user_obj - 避免 patch
sqlalchemy.orm.sessionmaker:它影响全局,且容易和测试框架的 session fixture 冲突 - 如果用了
scoped_session,确保 patch 的是调用sessionmaker()后返回的那个 callable,不是 sessionmaker 类本身
复杂点在于 ORM 查询链式调用的 mock 需要逐级打桩,容易漏掉中间某一级的 return_value;一旦某处没设,后续调用就变成 Mock 对象,不再是你预期的行为。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











