在测试逻辑时,我遇到了测试隔离的问题,这涉及SQLAlchemy中的事务回滚。
模型:
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
company = db.Column(db.Text)
subtype = db.Column(db.Text)
__table_args__ = (db.UniqueConstraint(company, subtype),)
查看:
def create():
instance = Product(**request.json)
db.session.add(instance)
try:
db.session.commit()
except IntegrityError:
db.session.rollback()
return {"detail": "Product object already exists", "status": 406, "title": "Duplicate object"}, 406
return {"uri": f"/products/{instance.id}"}, 201
测试:
DEFAULT_DATA = {"company": "Test", "subtype": "Sub"}
def test_create(client):
response = client.post("/products", json=DEFAULT_DATA)
assert response.status_code == 201
instance = Product.query.one()
assert response.json == {"uri": f"/products/{instance.id}"}
def test_create_duplicate(client):
response = client.post("/products", json=DEFAULT_DATA)
assert response.status_code == 201
instance = Product.query.one()
assert response.json == {"uri": f"/products/{instance.id}"}
response = client.post("/products", json=DEFAULT_DATA)
assert response.status_code == 406
assert response.json == {"detail": "Product object already exists", "status": 406, "title": "Duplicate object"}
conftest.py :
import flask_migrate
import pytest
from sqlalchemy import event
from project.app import create_connexion_app
from project.models import db
@pytest.fixture(scope="session")
def connexion_app():
return create_connexion_app("project.settings.TestSettings")
@pytest.fixture(scope="session")
def app(connexion_app):
app = connexion_app.app
with app.app_context():
yield app
@pytest.fixture(scope="session", name="db")
def db_setup(app):
flask_migrate.upgrade()
yield db
flask_migrate.downgrade()
db.engine.execute("DROP TABLE IF EXISTS alembic_version")
@pytest.fixture(autouse=True)
def session(db):
with db.engine.connect() as connection:
@event.listens_for(db.session, "after_transaction_end")
def restart_savepoint(session, transaction):
if transaction.nested and not transaction._parent.nested:
# ensure that state is expired the way
# session.commit() at the top level normally does
# (optional step)
session.expire_all()
session.begin_nested()
transaction = connection.begin()
db.session.begin_nested()
yield db.session
db.session.rollback()
db.session.close()
if transaction.is_active:
transaction.rollback()
SQLALCHEMY_COMMIT_ON_TEARDOWN
设置为False
第二项测试失败,并显示以下输出:
def test_create_duplicate(client):
response = client.post("/products", json=DEFAULT_DATA)
> assert response.status_code == 201
E AssertionError: assert 406 == 201
E + where 406 = <<class 'pytest_flask.plugin.JSONResponse'> streamed [406 NOT ACCEPTABLE]>.status_code
相关PG日志:
LOG: statement: BEGIN
LOG: statement: INSERT INTO product (company, subtype) VALUES ('Test', 'Sub') RETURNING product.id
LOG: statement: COMMIT
LOG: statement: BEGIN
LOG: statement: SELECT product.id AS product_id, product.company AS product_company, product.subtype AS product_subtype
FROM product
WHERE product.id = 1
LOG: statement: SELECT product.id AS product_id, product.company AS product_company, product.subtype AS product_subtype
FROM product
LOG: statement: ROLLBACK
LOG: statement: BEGIN
LOG: statement: INSERT INTO product (company, subtype) VALUES ('Test', 'Sub') RETURNING product.id
ERROR: duplicate key value violates unique constraint "product_company_subtype_key"
DETAIL: Key (company, subtype)=(Test, Sub) already exists.
STATEMENT: INSERT INTO product (company, subtype) VALUES ('Test', 'Sub') RETURNING product.id
LOG: statement: ROLLBACK
因此,第一个测试将一行提交到数据库中,并且在两次测试之间不会回滚,因此两次运行之间不会恢复数据库状态。
其他测试,但不涉及明确的回滚工作也可以。试图将SQLALCHEMY_COMMIT_ON_TEARDOWN
更改为True
并使用flush
代替commit
,但是在这种情况下,test_create_duplicate
之后的测试会受到影响。
如何设置测试套件来测试此类代码,其中涉及手动提交/回滚?
包装:
Python版本:3.6.6
RDBMS :PostgreSQL 10.4