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

时间:2015-12-04 02:48:22

标签: postgresql flask sqlalchemy flask-sqlalchemy pytest

我正在努力编写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 doc的底部有一个潜在的配方可以完成此任务。

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

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

    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
    

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

    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

2 个答案:

答案 0 :(得分:0)

尝试合并yield灯具时遇到了类似的问题。很遗憾,根据doc,您无法合并多个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

答案 1 :(得分:0)

我在这里迟到了几年,但是您可能对pytest-flask-sqlalchemy-transactions感兴趣,我写了一个插件来解决这个确切的问题。

该插件提供了两个夹具,db_sessiondb_engine,您可以使用它们像常规的Session和Engine对象一样运行更新,这些更新将在测试结束时回滚。它还公开了一些配置指令(mocked-enginesmocked-sessions),这些指令将在您的应用中模拟可连接对象并将其替换为这些固定装置,以便您可以运行方法并确保将清除所有状态更改测试退出时向上显示。

该插件应可用于多种数据库,但已针对Postgres 9.6进行了最严格的测试,并已在https://dedupe.io的测试套件中投入生产。您可以在the documentation中找到一些示例,这些示例应该可以帮助您入门,但是如果您愿意提供一些代码,我也很乐于演示如何使用该插件。