You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在pytest数据库初始化fixture中调用Alembic迁移无执行效果

问题:FastAPI测试中Alembic迁移在内存SQLite中不生效

为FastAPI项目配置pytest fixture,目标是在内存SQLite数据库上执行迁移完成测试环境初始化。但调用Alembic的upgrade命令后无任何效果,检查发现预期的players表并未创建。Alembic目录可被正常找到并调用,执行过程无任何报错提示。手动执行迁移可正常生成SQLite文件并创建所需的players表,通过FastAPI Swagger手动测试也可正常运行,仅在pytest fixture中出现此问题。

Pytest测试文件

from alembic import command
from alembic.config import Config
from fastapi.testclient import TestClient
import logging
from os import environ, path
import pytest
from sqlalchemy import create_engine, StaticPool, inspect
from sqlalchemy.orm import sessionmaker

from src.main import app
from database.models import get_db
from alembic import op
import sqlalchemy as sa


LOG_PATH = path.join(path.dirname(path.abspath(__file__)), '../tests/test.log')
logging.basicConfig(
    format='%(asctime)s - %(levelname)s - %(message)s',
    filename=LOG_PATH,
    filemode='a',
    level=logging.DEBUG,
)
logging.debug('Test Initalize')

client = TestClient(app)

TEST_DATABASE_URL = 'sqlite:///:memory:'

engine = create_engine(
    TEST_DATABASE_URL,
    connect_args={
        'check_same_thread': False,
    },
    poolclass=StaticPool,
)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# This method and the dependency_overide, overrides the get_db method in the main.py file
def override_get_db():
    """Override the database session for testing."""
    database = TestingSessionLocal()
    try:
        yield database
    finally:
        database.close()

app.dependency_overrides[get_db] = override_get_db


# Setup the database for testing
@pytest.fixture(scope='session', autouse=True)
def apply_migrations():
    """Setup the database for testing."""
    logging.debug('Applying migrations')
    engine = create_engine(TEST_DATABASE_URL)
    with engine.begin() as connection:
        alembic_cfg = Config('alembic.ini')
        alembic_cfg.attributes['configure_logger'] = True
        alembic_cfg.attributes['connection'] = connection
        alembic_cfg.set_main_option('script_location', './src/alembic')
        try:
            command.ensure_version(alembic_cfg)
            command.upgrade(alembic_cfg, 'head') # <- alembic upgrade here
            assert engine.dialect.has_table(engine, 'players') # <- error is found here, no table
        except Exception as e:
            logging.error(f'Exception during migration: {e}')
            raise e
        yield
        engine.dispose()

def test_create_player(apply_migrations):
    # arrange
    data = {
        'playerName': 'test_player',
        'password': 'test_password',
        'email': 'test@noplace.com',
        'isAdmin': False,
    }

    # act
    # inspector = inspect(engine)
    # tables = inspector.get_table_names()

    response = client.post(
        '/players/',
        json=data,
    )

    # assert
    assert response.status_code == 201
    response_data = response.json()
    assert response_data['playerId'] == 1
    assert response_data['playerName'] == 'test_player'
    assert response_data['password'] == 'test_password'
    assert response_data['email'] == 'test@noplace.com'
    assert response_data['isAdmin'] is False

迁移脚本

"""initial migration

Revision ID: 264e0a95c5f8
Revises: 
Create Date: 2023-12-30 18:53:59.435418

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '264e0a95c5f8'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('characters',
    sa.Column('characterId', sa.Integer(), nullable=False),
    sa.Column('characterName', sa.String(), nullable=True),
    sa.Column('playerId', sa.Integer(), nullable=True),
    sa.Column('isActive', sa.Boolean(), nullable=True),
    sa.PrimaryKeyConstraint('characterId')
    )
    op.create_table('players',
    sa.Column('playerId', sa.Integer(), nullable=False),
    sa.Column('playerName', sa.String(), nullable=True),
    sa.Column('password', sa.String(), nullable=True),
    sa.Column('email', sa.String(), nullable=True),
    sa.Column('isAdmin', sa.Boolean(), nullable=True),
    sa.Column('isActive', sa.Boolean(), nullable=True),
    sa.PrimaryKeyConstraint('playerId')
    )
    op.create_table('transactions',
    sa.Column('transactionId', sa.Integer(), nullable=False),
    sa.Column('characterId', sa.Integer(), nullable=True),
    sa.Column('amount', sa.Float(precision=2), nullable=True),
    sa.Column('description', sa.String(), nullable=True),
    sa.Column('transactionDate', sa.DateTime(), nullable=True),
    sa.PrimaryKeyConstraint('transactionId')
    )
    # ### end Alembic commands ###


def downgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('transactions')
    op.drop_table('players')
    op.drop_table('characters')
    # ### end Alembic commands ###

内容的提问来源于stack exchange,提问作者cantis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 04:28:10