Flask应用中PyTest未切换测试数据库致重复条目错误
问题背景
本地.env文件配置了两个数据库:
DATABASE_URI(生产/开发库)TEST_DB_URI(测试专用库)
运行PyTest测试套件时,频繁出现重复条目错误:
sqlalchemy.exc.IntegrityError: (pymysql.err.IntegrityError) (1062, "Duplicate entry 'test@example.com' for key 'users.email'")
原因是测试代码实际连接的是DATABASE_URI对应的数据库(该库已存在测试用户),而非预期的TEST_DB_URI测试库,即使在pytest fixture中手动修改了app.config["SQLALCHEMY_DATABASE_URI"]也未生效。
核心原因
你的app.py中,在初始化阶段就直接将app传入SQLAlchemy()创建了db实例:
db = SQLAlchemy(app)
这会导致db实例在程序启动时就绑定了当时的SQLALCHEMY_DATABASE_URI配置(即DATABASE_URI)。后续在pytest fixture中修改app.config,并不会更新已创建的db实例的连接配置,测试代码依然会使用初始绑定的生产库。
另外,原fixture的执行顺序存在错误:在yield client之前就执行了db.drop_all(),导致测试运行时数据库表已被删除,这也是潜在问题。
解决方案
1. 修改Flask扩展初始化方式
将SQLAlchemy、Migrate等扩展的初始化延迟,先创建空实例,再通过init_app()绑定到app,这样测试时可以重新绑定配置。
修改app.py:
from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager from flask_migrate import Migrate # 创建Flask app app = Flask(__name__, static_url_path="/static") # 加载基础配置 app.config.from_pyfile("config.py") # 先创建空的扩展实例,不直接传入app db = SQLAlchemy() migrate = Migrate() login_manager = LoginManager() # 初始化扩展,绑定到app db.init_app(app) migrate.init_app(app, db) login_manager.init_app(app)
2. 修正Pytest Fixture逻辑
在fixture中修改app配置后,重新初始化db确保使用测试数据库,同时调整执行顺序,确保测试完成后再清理数据库。
修改tests/__init__.py:
import os import pytest from app import app, db from models.user import User @pytest.fixture def client(): # 覆盖配置,启用测试模式并指定测试数据库 app.config["TESTING"] = True app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("TEST_DB_URI") # 重新初始化db,确保使用新的配置 db.init_app(app) with app.test_client() as client: with app.app_context(): # 创建测试所需的所有表 db.create_all() # 添加测试用户 user = User(email="test@example.com", password="test_password") db.session.add(user) db.session.commit() # 将client返回给测试用例执行 yield client # 测试完成后,清理数据库 with app.app_context(): db.session.remove() db.drop_all()
3. 优化基础配置(可选)
修改config.py,将TESTING默认设置为False,避免开发环境默认使用测试配置:
import os from dotenv import load_dotenv load_dotenv() SECRET_KEY = os.environ['SESSION_KEY'] SQLALCHEMY_DATABASE_URI = os.environ['DATABASE_URI'] SQLALCHEMY_TRACK_MODIFICATIONS = True # General Config ENVIRONMENT = "development" FLASK_APP = "app" FLASK_DEBUG = True TESTING = False # 改为False,测试时由fixture覆盖
验证方法
运行测试套件后,检查TEST_DB_URI对应的数据库,会发现测试用户被正确创建并在测试结束后自动删除,同时生产库不会再被测试代码修改。
内容的提问来源于stack exchange,提问作者Quinn Moffat

