我正在尝试应用此处描述的配方:http://alexmic.net/flask-sqlalchemy-pytest/,以使用pytest来使用Flask-SQLAlchemy测试应用程序。
写作测试部分中指出:
请注意,我们可以像往常一样自由提交会话。之所以能够实现这一目标,是因为会话“加入了”由我们在会话固定装置中显式创建的连接所创建的外部事务,因此只有最外面的BEGIN / COMMIT对才起作用。
但是,如果我在测试中使用session.add
方法,这对我不起作用。但是,当我改用session.execute
时,它可以工作。
以下是显示问题的示例:
database.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
models.py
from database import db
class Employee(db.Model):
__tablename__ = "employee"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
test_employee.py
from models import Employee
def test_employee(session):
my_employee = Employee()
my_employee.name = "Toto"
session.add(my_employee)
# session.execute("INSERT INTO employee (id, name) VALUES (1, 'Tata');")
session.commit()
conftest.py (根据示例略作改编)
import os
import pytest
from flask import Flask
from database import db as _db
TESTDB = 'test_utte.db'
TESTDB_PATH = "/tmp/{}".format(TESTDB)
TEST_DATABASE_URI = 'sqlite:///' + TESTDB_PATH
@pytest.fixture(scope='session')
def app(request):
"""Session-wide test `Flask` application."""
settings_override = {
'TESTING': True,
'SQLALCHEMY_DATABASE_URI': TEST_DATABASE_URI,
'SQLALCHEMY_TRACK_MODIFICATIONS': False # Disabled to remove warning
}
app = Flask(__name__)
app.config.update(settings_override)
# Establish an application context before running the tests.
ctx = app.app_context()
ctx.push()
def teardown():
ctx.pop()
request.addfinalizer(teardown)
return app
@pytest.fixture(scope='session')
def db(app, request):
"""Session-wide test database."""
if os.path.exists(TESTDB_PATH):
os.unlink(TESTDB_PATH)
def teardown():
pass # Commented to access database after test is run
# _db.drop_all()
# os.unlink(TESTDB_PATH)
_db.init_app(app)
_db.create_all()
request.addfinalizer(teardown)
return _db
@pytest.fixture(scope='function')
def session(db, request):
"""Creates a new database session for a test."""
db.engine.echo = True
connection = db.engine.connect()
transaction = connection.begin()
options = dict(bind=connection, binds={})
session = db.create_scoped_session(options=options)
db.session = session
def teardown():
transaction.rollback()
connection.close()
session.remove()
request.addfinalizer(teardown)
return session
运行pytest -s
给出以下日志:
test_employee.py 2018-07-10 13:22:48,645 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-07-10 13:22:48,648 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-07-10 13:22:48,650 INFO sqlalchemy.engine.base.Engine INSERT INTO employee (name) VALUES (?)
2018-07-10 13:22:48,650 INFO sqlalchemy.engine.base.Engine ('Toto',)
2018-07-10 13:22:48,651 INFO sqlalchemy.engine.base.Engine COMMIT
.2018-07-10 13:22:48,657 INFO sqlalchemy.engine.base.Engine ROLLBACK
如果我在数据库中签入,则条目在此处
sqlite> select * from employee;
1|Toto
相反,如果我将 test_employee.py 替换为session.execute部分:
from models import Employee
def test_employee(session):
# my_employee = Employee()
# my_employee.name = "Toto"
# session.add(my_employee)
session.execute("INSERT INTO employee (id, name) VALUES (1, 'Tata');")
session.commit()
我得到以下日志:
test_employee.py 2018-07-10 13:28:27,093 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-07-10 13:28:27,095 INFO sqlalchemy.engine.base.Engine INSERT INTO employee (id, name) VALUES (1, 'Tata');
2018-07-10 13:28:27,096 INFO sqlalchemy.engine.base.Engine ()
.2018-07-10 13:28:27,098 INFO sqlalchemy.engine.base.Engine ROLLBACK
并且该条目不在数据库中:
sqlite> select * from employee;
sqlite>
最后,如果我同时参加了测试:
from models import Employee
def test_employee(session):
my_employee = Employee()
my_employee.name = "Toto"
session.add(my_employee)
session.execute("INSERT INTO employee (id, name) VALUES (1, 'Tata');")
session.commit()
然后我有类似的情况,只有session.execute
test_employee.py 2018-07-10 13:31:57,179 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-07-10 13:31:57,180 INFO sqlalchemy.engine.base.Engine INSERT INTO employee (id, name) VALUES (1, 'Tata');
2018-07-10 13:31:57,181 INFO sqlalchemy.engine.base.Engine ()
2018-07-10 13:31:57,182 INFO sqlalchemy.engine.base.Engine INSERT INTO employee (name) VALUES (?)
2018-07-10 13:31:57,182 INFO sqlalchemy.engine.base.Engine ('Toto',)
.2018-07-10 13:31:57,183 INFO sqlalchemy.engine.base.Engine ROLLBACK
该表仍然为空:
sqlite> select * from employee;
sqlite>
我的猜测是这与我的第一个示例中的日志中显示的内部 BEGIN / COMMIT 有关,但我并不真正理解为什么它在这里或为什么它覆盖了最外面的 BEGIN / ROLLBACK ?
答案 0 :(得分:1)
这是一个非常棘手的问题,我想知道这是否与在sqlite中实现session.execute
有关。我从未在Postgres中看到过类似的东西。
虽然这不能解决问题的根源,但我想知道使用pytest-flask-sqlalchemy-transactions是否可以解决您的问题。该插件旨在解决这个确切的用例,并公开了一个db_session
固定装置,可以满足您的需求。
您需要设置一个_db
固定装置,以使插件可以访问您的数据库:
@pytest.fixture
def _db(db):
return db
查看the docs以获得安装说明,并告诉我它是否适合您。它尚未经过sqlite的广泛测试,因此至少可以帮助您确定这是否是sqlite特有的问题。
答案 1 :(得分:1)
由于jeancochrane's answer,我设置了一个新的virtualenv来安装pytest-flask-sqlalchemy-transactions
,却发现我什至无法重现问题!
经过搜索和比较后,我发现问题出在我最初使用带有Flask-SQLAlchemy 2.1.0的venv的事实。
升级到下一版本(Flask-SQLAlchemy 2.2.0)时,问题消失了。
- 允许监听db.session上的SQLAlchemy事件
也许是引起此问题的原因?我不知道某些Flask-SQLAlchemy开发人员是否能够回答这个问题?