修改Pytest Fixture中SQLALCHEMY_DATABASE_URI仍用生产库的问题
问题
我正在学习flask-sqlalchemy库,使用Flask框架开发应用,并通过Pytest结合fixtures进行测试。尝试在测试fixtures中覆盖应用配置里的sqlalchemy_database_uri,但测试仍在使用“生产”数据库而非临时数据库——通过fixtures创建的测试记录都出现在应用初始化时创建的数据库中。
按以下目录结构,在根目录执行pytest后,测试可正常收集、运行并通过,但测试结束后instance文件夹中生成了新的hello.db数据库。希望测试使用运行后即消失的内存型SQLite数据库,请问该如何编写/配置测试以实现这一需求?
# 目录结构 . ├── mini_app │ ├── __init__.py │ ├── app.py │ ├── config.py │ └── models.py └── tests ├── __init__.py └── test_all.py
现有代码
app.py
from flask import Flask, request from flask_sqlalchemy import SQLAlchemy from mini_app import config db = SQLAlchemy() def create_app(cfg= None): if cfg is None: cfg = config.Config() app = Flask(__name__) app.config.from_object(cfg) # Init_apps db.init_app(app) @app.route("/square") def square(): """A view which uses no templates and no database.""" number = int(request.args.get("number", 0)) return str(number ** 2) return app
config.py
class Config: SQLALCHEMY_DATABASE_URI = "sqlite:///hello.db"
models.py
from mini_app.app import db class Author(db.Model): __tablename__ = "authors" id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String)
test_all.py
import pytest import mini_app.app import mini_app.models @pytest.fixture def client(): # Prepare before your test app = mini_app.app.create_app() app.config["TESTING"] = True app.testing = True app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite://" with app.test_client() as client: with app.app_context(): mini_app.app.db.create_all() author1 = mini_app.models.Author(id=1, name="foo") mini_app.app.db.session.add(author1) mini_app.app.db.session.commit() # Give control to the test yield client # Cleanup after the test run. # ... nothing here, for this simple example def test_square(client): rv = client.get("/square?number=8") assert b"64" == rv.data
解决方案
问题根源
当前写法是先调用create_app()初始化应用(此时已加载默认Config的数据库URI),之后再修改app.config。但db.init_app(app)已经在初始化应用时执行,SQLAlchemy已经绑定了原数据库URI,后续修改配置不会生效,导致测试依然使用文件型SQLite数据库。
修正方案
方式一:直接在创建应用时传入测试配置
修改test_all.py的fixture,确保在create_app时就传入测试专用配置,让SQLAlchemy初始化时绑定内存数据库:
import pytest from mini_app.app import create_app, db from mini_app.models import Author @pytest.fixture def app(): # 定义测试专用配置 test_config = { "TESTING": True, "SQLALCHEMY_DATABASE_URI": "sqlite://", "SQLALCHEMY_TRACK_MODIFICATIONS": False # 关闭修改追踪,避免不必要的警告 } # 创建应用并加载测试配置 app = create_app() app.config.from_mapping(test_config) with app.app_context(): # 创建数据表并添加测试数据 db.create_all() author1 = Author(id=1, name="foo") db.session.add(author1) db.session.commit() yield app # 测试后清理:移除会话并删除所有表 db.session.remove() db.drop_all() @pytest.fixture def client(app): # 基于测试应用生成客户端 return app.test_client() def test_square(client): rv = client.get("/square?number=8") assert b"64" == rv.data
方式二:添加测试专用配置类(更适合大型项目)
在config.py中新增测试配置类,让测试直接调用该类:
# config.py class Config: SQLALCHEMY_DATABASE_URI = "sqlite:///hello.db" SQLALCHEMY_TRACK_MODIFICATIONS = False class TestConfig(Config): TESTING = True SQLALCHEMY_DATABASE_URI = "sqlite://"
然后修改test_all.py的fixture:
import pytest from mini_app.app import create_app, db from mini_app.config import TestConfig from mini_app.models import Author @pytest.fixture def app(): # 直接传入测试配置类创建应用 app = create_app(cfg=TestConfig) with app.app_context(): db.create_all() author1 = Author(id=1, name="foo") db.session.add(author1) db.session.commit() yield app db.session.remove() db.drop_all() @pytest.fixture def client(app): return app.test_client() def test_square(client): rv = client.get("/square?number=8") assert b"64" == rv.data
关键说明
- 必须确保SQLAlchemy初始化(
db.init_app(app))时,应用已经加载了测试配置的数据库URI,否则后续修改配置不会生效。 - 拆分
app和clientfixture,既可以让测试复用应用实例,也能更清晰地管理应用的生命周期和清理逻辑。 - 测试后添加
db.session.remove()和db.drop_all(),确保内存数据库的资源被正确释放,避免测试间的状态污染。
内容的提问来源于stack exchange,提问作者Alvaro Aguilar
相关产品推荐
相关产品推荐

