如何在SQLAlchemy中测试回滚

时间:2018-07-11 12:59:50

标签: python-3.x postgresql transactions sqlalchemy pytest

在测试逻辑时,我遇到了测试隔离的问题,这涉及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之后的测试会受到影响。

如何设置测试套件来测试此类代码,其中涉及手动提交/回滚?

包装

  • 烧瓶== 1.0.2
  • Flask-Migrate == 2.2.1
  • Flask-SQLAlchemy == 2.3.2
  • SQLAlchemy == 1.2.9
  • dictalchemy == 0.1.2.7
  • connexion == 1.4.2
  • pytest == 3.6.2
  • pytest-flask == 0.10.0

Python版本:3.6.6

RDBMS :PostgreSQL 10.4

0 个答案:

没有答案