测试基于SQLModel的Typer CLI应用时的SQLite临时数据库连接问题与方案咨询
测试基于SQLModel的Typer CLI应用时的SQLite临时数据库连接问题与方案咨询
最近在给用Typer+SQLModel写的CLI应用写测试,想把测试环境的数据库换成pytest临时目录里的SQLite库,结果踩了好几个坑,现在把问题整理出来,求大家帮忙看看怎么解决,或者有没有更优雅的方案。
问题背景
我原本的应用用SQLModel操作本地的database.db,测试时想换成pytest的tmp_path生成的临时数据库,这样测试不会污染真实数据,而且还想用原生sqlite3语句去断言数据库里的内容。但过程中接连遇到两个问题:
遇到的具体问题
- 一开始用sqlite3连接临时库时,直接报
sqlite3.OperationalError: unable to open database file,后来才发现是连接路径的格式错了; - 修正路径后,又出现
sqlite3.OperationalError: no such table: project——我去临时目录里看,db.db确实存在,但是空的!但SQLModel的日志明明显示已经执行了建表和插入操作,这说明SQLModel根本没操作我指定的临时库,还是在操作原来的数据库。
我的代码片段
测试文件
import sqlite3 from typer.testing import CliRunner from projects import db from projects.app_typer import app def temp_db(path): db.sqlite_url = f"sqlite:///{path}/db.db" runner = CliRunner() # 这个测试是过的,说明sqlite_url确实被修改了 def test_url_replacement(tmp_path): temp_db(tmp_path) assert db.sqlite_url == f"sqlite:///{tmp_path}/db.db" # 这个测试就出问题了 def test_add_item_to_db(tmp_path): temp_db(tmp_path) # 调用CLI的add命令添加项目 result = runner.invoke(app, ["add", "public", "-n", "Project", "-p", "00-00"]) # 这里一开始写错了路径格式,后来改成了直接传文件路径 con = sqlite3.connect(tmp_path / "db.db") cur = con.cursor() db_entry = cur.execute("SELECT * FROM project").fetchone() print(db_entry)
db.py核心代码
from sqlmodel import Field, Session, SQLModel, create_engine, select # 默认的数据库路径 sqlite_url = "sqlite:///database.db" # 导入模块时就创建了引擎 engine = create_engine(sqlite_url) def create_session_and_db(): # 用上面的引擎创建表 SQLModel.metadata.create_all(engine)
app_typer.py核心代码
import typer from projects.db import create_session_and_db from projects.add import app as add_app app = typer.Typer(add_completion=False) # 每次调用CLI都会先执行这个回调,创建表和会话 @app.callback(invoke_without_command=True, no_args_is_help=True) def main(): create_session_and_db() app.add_typer(add_app, name="add", help="Add a project to the DB.")
我自己排查出的点
- 第一个问题的原因:SQLModel用的是带
sqlite:///前缀的URI格式,但原生sqlite3的connect()方法只需要直接传文件路径(不管是字符串还是pathlib对象),不需要前缀,改成sqlite3.connect(tmp_path / "db.db")就解决了连接问题。 - 第二个问题的根源:engine是在导入db.py的时候就创建好了,之后我修改
db.sqlite_url只是改了个变量,已经创建的engine实例根本没变化,所以CLI执行create_session_and_db()时,还是用原来的引擎操作原来的数据库,临时库自然是空的。
想请教大家的问题
- 我现在的思路是修改全局的
sqlite_url来切换数据库,但显然因为引擎初始化时机的问题行不通,有没有更合适的方式在测试中替换数据库? - 应该怎么重构db.py的代码,才能让测试时可以动态切换数据库引擎?比如把引擎的创建改成懒加载,或者用工厂模式?
备注:内容来源于stack exchange,提问作者Jan
相关产品推荐
相关产品推荐

