SQLite内存数据库:memory:天然隔离,每次测试新建空库、结束即销毁,需在setup中调用create_engine("sqlite:///:memory:")并立即执行Base.metadata.create_all(engine),配合scope="function"的pytest fixture管理生命周期,确保环境一致、无副作用。

用 sqlite:///:memory: 创建隔离的测试数据库
单元测试必须保证每次运行环境一致、无副作用,sqlite:///:memory: 是最轻量且天然隔离的方案——每个测试用例启动时新建一个空内存数据库,结束即销毁,不污染磁盘或共享状态。
注意:不能写成 sqlite:///test.db 或 sqlite://(后者会默认连接到文件系统当前目录下的 main.db),也别用 sqlite:///:memory:?check_same_thread=False 来“绕过线程限制”,这反而掩盖了多线程测试中暴露的真实问题。
- 在测试 setup 阶段调用
create_engine("sqlite:///:memory:"),再传给SQLAlchemy()实例 - 务必在每次测试前执行
Base.metadata.create_all(engine),否则表不存在,INSERT会直接报OperationalError: no such table - 如果模型用了
__table_args__ = {"schema": "xxx"},SQLite 不支持 schema,会静默忽略或报错,测试时需临时移除或改用 PostgreSQL 的pg8000+pytest-postgresql
避免在测试中复用生产 sessionmaker
生产代码里常定义全局 SessionLocal = sessionmaker(bind=engine),但测试中若直接 import 并调用 SessionLocal(),就会绑定到生产 engine(比如 PostgreSQL),导致测试连错库、变集成测试、甚至删掉真实数据。
正确做法是为测试单独构造 sessionmaker,并确保它绑定的是内存引擎:
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
立即学习“Python免费学习笔记(深入)”;
from sqlalchemy.orm import sessionmaker <h1>测试专用</h1><p>TestSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=test_engine) db = TestSessionLocal()
- 不要用
sessionmaker(bind=engine)()这种临时调用,容易漏关 session 或没 rollback - 测试函数结束后必须显式调用
db.close();更稳妥的是用 pytest fixture 管理生命周期 - 若测试涉及事务回滚(比如想测“插入后查不到”),记得在
db.rollback()后再db.close(),否则未提交的变更可能被意外提交
用 pytest fixture 管理 engine/session 生命周期
手动创建/销毁 engine 和 session 容易遗漏,用 fixture 能强制约束顺序和作用域。推荐使用 scope="function" 级别,确保每个测试干净独立。
@pytest.fixture(scope="function")
def db_session():
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
session = TestingSessionLocal()
yield session
session.close()
engine.dispose()-
engine.dispose()必须加,否则 SQLite 内存 DB 可能残留,尤其在 Windows 下引发Database is locked - 不要把 engine 做成 module 级 fixture(
scope="module"),多个测试并发跑时会因共享 engine 出现竞争 - 如果测试需要预置数据,直接在 fixture 里
session.add()+session.commit(),但注意 commit 后无法 rollback,此时应改用session.begin_nested()配合 savepoint
慎用 Base.metadata.drop_all() 清库
有些教程建议在 teardown 时调用 drop_all(),这在 SQLite 内存库中完全多余——:memory: 本身生命周期就只在 engine 存活期间,engine.dispose() 后数据自动消失。强行 drop_all() 反而可能触发 “no such table” 报错,尤其当某些模型没被 import 导致 Base.metadata.tables 不全时。
- 唯一需要
drop_all()的场景是:你用了真实文件数据库(如sqlite:///test.db)且想重用该文件 —— 但这就违背了单元测试隔离原则 - 如果模型间有外键依赖,
create_all()顺序错误会导致IntegrityError;确保所有模型模块在Base之后 import,或显式按依赖顺序调用create_table() - ORM 层的
delete()操作不会级联删关联记录,除非模型声明了cascade="all, delete-orphan",测试时别假设它会自动清理
测试真正难的不是建库,而是让 model、session、transaction、fixture 四者生命周期对齐——少一个 close(),多一个 commit(),都可能让下一个测试莫名其妙失败。

















