如何将py.test fixtures与Flask-SQLAlchemy和PostgreSQL结合起来?

Jef*_*man 9 postgresql sqlalchemy pytest flask flask-sqlalchemy

我正在努力编写py.test fixtures来管理我的应用程序数据库,以最大限度地提高速度,支持pytest-xdist测试的并行化,并将测试彼此隔离开来.

我正在对PostgreSQL 9.4数据库使用Flask-SQLAlchemy 2.1.

以下是我要完成的大致概述:

  1. $ py.test -n 3 旋转三个测试会话以运行测试.

  2. 在每个会话中,py.test fixture运行一次以设置事务,创建数据库表,然后在会话结束时回滚事务.创建数据库表需要在PostgreSQL事务中发生,该事务仅对该特定测试会话可见,否则由pytest-xdist原因创建的并行化测试会话会相互冲突.

  3. 为每个测试运行的第二个py.test fixture连接到现有事务,以便查看创建的表,创建嵌套保存点,运行测试,然后回滚到嵌套保存点.

  4. 理想情况下,这些pytest灯具支持调用的测试db.session.rollback().在这个SQLAlchemy文档的底部有一个潜在的配方来完成这个.

  5. 理想情况下,pytest fixtures应该产生db对象,而不仅仅是会话,以便人们可以编写测试而不必记住使用与db.session他们在整个应用程序中使用的标准不同的会话.

这是我到目前为止所拥有的:

import pytest

# create_app() is my Flask application factory
# db is just 'db = SQLAlchemy()' + 'db.init_app(app)' within the create_app() function
from app import create_app, db as _db 


@pytest.yield_fixture(scope='session', autouse=True)
def app():
    '''Session-wide test application'''
    a = create_app('testing')
    with a.app_context():
        yield a

@pytest.yield_fixture(scope='session')
def db_tables(app):
    '''Session-wide test database'''
    connection = _db.engine.connect()
    trans = connection.begin() # begin a non-ORM transaction

    # Theoretically this creates the tables within the transaction
    _db.create_all()
    yield _db
    trans.rollback()
    connection.close()

@pytest.yield_fixture(scope='function')
def db(db_tables):
    '''db session that is joined to existing transaction'''

    # I am quite sure this is broken, but it's the general idea 

    # bind an individual Session to the existing transaction
    db_tables.session = db_tables.Session(bind=db_tables.connection)

    # start the session in a SAVEPOINT...
    db_tables.session.begin_nested()

    # yield the db object, not just the session so that tests
    # can be written transparently using the db object
    # without requiring someone to understand the intricacies of these
    # py.test fixtures or having to remember when to use a session that's
    # different than db.session
    yield db_tables

    # rollback to the savepoint before the test ran
    db_tables.session.rollback()
    db_tables.session.remove() # not sure this is needed
Run Code Online (Sandbox Code Playgroud)

这是我在google搜索时发现的最有用的参考资料:

http://docs.sqlalchemy.org/en/latest/orm/session_transaction.html#joining-a-session-into-an-external-transaction-such-as-for-test-suites

http://koo.fi/blog/2015/10/22/flask-sqlalchemy-and-postgresql-unit-testing-with-transaction-savepoints/

https://github.com/mitsuhiko/flask-sqlalchemy/pull/249

Toc*_*Toc 0

我在尝试组合yield灯具时遇到了类似的问题。不幸的是,根据文档,您无法组合多个yield级别。

但您也许可以使用以下方法找到解决方法request.finalizer

@pytest.fixture(scope='session', autouse=True)
def app():
    '''Session-wide test application'''
    a = create_app('testing')
    with a.app_context():
        return a

@pytest.fixture(scope='session')
def db_tables(request, app):
    '''Session-wide test database'''
    connection = _db.engine.connect()
    trans = connection.begin() # begin a non-ORM transaction

    # Theoretically this creates the tables within the transaction
    _db.create_all()
    def close_db_session():
        trans.rollback()
        connection.close()
    request.addfinalizer(close_db_session)
    return _db
Run Code Online (Sandbox Code Playgroud)