为什么我的 pytest 测试会在删除 SQLAlchemy DB 之前挂起?

art*_*pov 4 postgresql sqlalchemy flask python-3.x flask-sqlalchemy

这是我的 conftest.py (为简洁起见删除了一些代码)

from trip_planner import create_app, db as _db
from trip_planner.models import User
from test import TestConfig, test_instance_dir


@pytest.fixture(scope='session', autouse=True)
def app(session_mocker: pytest_mock.MockerFixture):
    static_folder = mkdtemp(prefix='static')
    _app = create_app(TestConfig(), instance_path=test_instance_dir,
                      static_folder=static_folder)
    ctx = _app.app_context()
    ctx.push()
    session_mocker.patch('trip_planner.assets.manifest',
                         new=defaultdict(str))

    yield _app

    ctx.pop()
    os.rmdir(static_folder)


@pytest.fixture(scope='session')
def db(app):
    _db.create_all()
    seed_db(_db)

    yield _db

    _db.drop_all()


def seed_db(db) -> User:
    sessionmaker = db.create_session({'autocommit': False})
    session = sessionmaker()

    user = User(username='username',
                password_digest=bcrypt.hash('password'))
    session.add(user)

    session.commit()
    session.close()
    return user


@pytest.fixture(scope='function')
def db_session(db):
    session = db.create_scoped_session(options=dict(
        autocommit=False, autoflush=False
        ))

    db.session = session
    with session.begin_nested():
        yield session

    session.rollback()
    session.remove()


@pytest.fixture(scope='function')
def app_client(app):
    with app.test_client() as c:
        yield c


@pytest.fixture(scope='function')
def session_user(db_session, app_client) -> int:
    user_id, = db_session.query(User.id).filter_by(username='username').one()
    with app_client.session_transaction() as sess:
        sess['user_id'] = user_id
    return user_id
Run Code Online (Sandbox Code Playgroud)

当我的测试通过时, pytest 会挂起。我只能用 来阻止它killall。对测试数据库的检查表明,这些关系实际上并未被删除。

我该如何补救?

art*_*pov 9

显然,这是 PostgreSQL 的一个众所周知的问题,这里是讨论

我解决这个问题的方法是_db.close_all_sessions()在删除所有表之前添加:

@pytest.fixture(scope='session')
def db(app):
    _db.create_all()
    seed_db(_db)

    yield _db

    _db.close_all_sessions()
    _db.drop_all()
Run Code Online (Sandbox Code Playgroud)