[英]Isolating py.test DB sessions in Flask-SQLAlchemy
I'm trying to build a Flask app with Flask-SQLAlchemy;我正在尝试使用 Flask-SQLAlchemy 构建 Flask 应用程序; I use pytest to test the DB.
我使用 pytest 来测试数据库。 One of the problems seems to be creating isolated DB sessions between different tests.
问题之一似乎是在不同测试之间创建隔离的数据库会话。
I cooked up a minimal, complete example to highlight the problem, note that test_user_schema1()
and test_user_schema2()
are the same.我编写了一个最小的、完整的示例来突出问题,注意
test_user_schema1()
和test_user_schema2()
是相同的。
Filename: test_db.py
文件名:
test_db.py
from models import User
def test_user_schema1(session):
person_name = 'Fran Clan'
uu = User(name=person_name)
session.add(uu)
session.commit()
assert uu.id==1
assert uu.name==person_name
def test_user_schema2(session):
person_name = 'Stan Clan'
uu = User(name=person_name)
session.add(uu)
session.commit()
assert uu.id==1
assert uu.name==person_name
If the db is truly isolated between my tests, both tests should pass.如果数据库在我的测试之间真正隔离,则两个测试都应该通过。 However, the last test always fails, because I haven't found a way to make db sessions rollback correctly.
但是,最后一次测试总是失败,因为我还没有找到正确回滚数据库会话的方法。
conftest.py
uses the following based on what I saw in Alex Michael's blog post , but this fixture code breaks because it apparently doesn't isolate the db sessions between fixtures. conftest.py
根据我在Alex Michael 的博客文章中看到的内容使用以下内容,但此夹具代码中断,因为它显然没有隔离夹具之间的数据库会话。
@pytest.yield_fixture(scope='function')
def session(app, db):
connection = db.engine.connect()
transaction = connection.begin()
#options = dict(bind=connection, binds={})
options = dict(bind=connection)
session = db.create_scoped_session(options=options)
yield session
# Finalize test here
transaction.rollback()
connection.close()
session.remove()
For the purposes of this question, I built a gist , which contains all you need to reproduce it;出于这个问题的目的,我构建了一个 gist ,其中包含重现它所需的所有内容; you can clone it with
git clone https://gist.github.com/34fa8d274fc4be240933.git
.你可以用
git clone https://gist.github.com/34fa8d274fc4be240933.git
克隆它。
I am using the following packages...我正在使用以下软件包...
Flask==0.10.1
Flask-Bootstrap==3.3.0.1
Flask-Migrate==1.3.0
Flask-Moment==0.4.0
Flask-RESTful==0.3.1
Flask-Script==2.0.5
Flask-SQLAlchemy==2.0
Flask-WTF==0.11
itsdangerous==0.24
pytest==2.6.4
Werkzeug==0.10.1
The method introduced in Alex Michael's blog post is not working because it's incomplete. Alex Michael 的博客文章中介绍的方法不起作用,因为它不完整。 According to the sqlalchemy documentation on joining sessions , Alex's solution works only if there are no rollback calls.
根据有关加入会话的sqlalchemy 文档,Alex 的解决方案仅在没有回滚调用时才有效。 Another difference is, a vanilla
Session
object is used in sqla docs, compared to a scoped session on Alex's blog.另一个区别是,与 Alex 博客上的作用域会话相比,sqla 文档中使用了 vanilla
Session
对象。
In the case of flask-sqlalchemy, the scoped session is automatically removed on request teardown .在flask-sqlalchemy 的情况下,范围会话会在请求拆卸时自动删除。 A call to
session.remove
is made, which issues a rollback under the hood.调用
session.remove
会在session.remove
发出回滚。 To support rollbacks within the scope of the tests, use SAVEPOINT
:要支持测试范围内的回滚,请使用
SAVEPOINT
:
import sqlalchemy as sa
@pytest.yield_fixture(scope='function')
def db_session(db):
"""
Creates a new database session for a test. Note you must use this fixture
if your test connects to db.
Here we not only support commit calls but also rollback calls in tests.
"""
connection = db.engine.connect()
transaction = connection.begin()
options = dict(bind=connection, binds={})
session = db.create_scoped_session(options=options)
session.begin_nested()
# session is actually a scoped_session
# for the `after_transaction_end` event, we need a session instance to
# listen for, hence the `session()` call
@sa.event.listens_for(session(), 'after_transaction_end')
def restart_savepoint(sess, trans):
if trans.nested and not trans._parent.nested:
session.expire_all()
session.begin_nested()
db.session = session
yield session
session.remove()
transaction.rollback()
connection.close()
Your database must support SAVEPOINT
though.不过,您的数据库必须支持
SAVEPOINT
。
1. 1.
According to Session Basics - SQLAlchemy documentation :根据会话基础 - SQLAlchemy 文档:
commit()
is used to commit the current transaction.commit()
用于提交当前事务。 It always issues flush() beforehand to flush any remaining state to the database;它总是预先发出 flush() 来将任何剩余状态刷新到数据库; this is independent of the “autoflush” setting.
这与“自动冲洗”设置无关。 ....
....
So transaction.rollback()
in session fixture function does not take effect, because the transaction is already committed.所以sessionfixture函数中的
transaction.rollback()
不会生效,因为事务已经提交了。
2. 2.
Change scope of fixtures to function
instead of session
so that db is cleared every time.将设备的范围更改为
function
而不是session
以便每次都清除 db。
@pytest.yield_fixture(scope='function')
def app(request):
...
@pytest.yield_fixture(scope='function')
def db(app, request):
...
BTW, If you use in-memory sqlite database, you don't need to delete the db files, and it will be faster:顺便说一句,如果您使用内存中的sqlite数据库,则不需要删除db文件,而且速度会更快:
DB_URI = 'sqlite://' # SQLite :memory: database
...
@pytest.yield_fixture(scope='function')
def db(app, request):
_db.app = app
_db.create_all()
yield _db
_db.drop_all()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.