Pytest Flask应用程序的数据库无法正常工作

时间:2018-09-08 09:29:00

标签: flask pytest

我有一个flask应用程序,并尝试使用pytest对其进行测试。问题是如何使基础为每个测试功能创建一个新的基础?我认为这应该起作用。

这是我的模特。

models.py

class User(UserMixin, db.Model):  # type: ignore
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    password_hash = db.Column(db.String(128))

    def set_password(self, password: str) -> None:
        self.password_hash = generate_password_hash(password)  # type: ignore

    def check_password(self, password) -> None:
        return check_password_hash(self.password_hash, password)  # type:ignore

class Employee(db.Model):  # type: ignore
    query_class = EmployeeSearch
    __tablename__ = 'employee'

    id = db.Column(db.Integer, primary_key=True)
    first_name = db.Column(db.String(60))
    patronymic = db.Column(db.String(60))
    last_name = db.Column(db.String(60))
    position = db.Column(db.String(60))
    office = db.Column(db.String)
    birthday = db.Column(db.Date)
    sex = db.Column(db.String)

我正在尝试在每个功能之后删除会话并删除更改。

conftest.py

import os

import pytest

from backend.auth import db as _db
from backend.auth import create_app
from backend.auth.models import User


class TestConfig(object):
    TESTING = True
    WTF_CSRF_ENABLED = False
    SECRET_KEY = os.environ.get('SECRET_KEY', 'JustaSecret')
    SQLALCHEMY_DATABASE_URI = 'postgresql://postgres:admin@localhost/FlaskTest'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    DEBUG = True


@pytest.yield_fixture(scope='session')
def app(request):
    flask_app = create_app(TestConfig)

    # Flask provides a way to test your application by exposing the Werkzeug test Client
    # and handling the context locals for you.

    testing_client = flask_app.test_client()

    # Establish an application context before running the tests.
    ctx = flask_app.app_context()
    ctx.push()

    def teardown():
        ctx.pop()

    request.addfinalizer(teardown)
    return testing_client


@pytest.fixture(scope='session')
def db(app, request):
    # Create the database and the database table
    def teardown():
        _db.drop_all()

    _db.app = app
    _db.create_all()
    user = User(username='admin')
    user.set_password('12345')
    _db.session.add(user)
    _db.session.commit()
    # Commit the changes for the users

    request.addfinalizer(teardown)
    return _db


@pytest.fixture(scope='function')
def session(db, request):
    """Creates a new database session for a test."""
    # connect to the database
    connection = db.engine.connect()
    # begin a non-ORM transaction
    transaction = connection.begin()

    # bind an individual session to the connection
    options = dict(bind=connection, binds={})
    session = db.create_scoped_session(options=options)

    # overload the default session with the session above
    db.session = session

    def teardown():
        session.close()
        # rollback - everything that happened with the
        # session above (including calls to commit())
        # is rolled back.
        transaction.rollback()
        # return connection to the Engine
        connection.close()
        session.remove()

    request.addfinalizer(teardown)
    return session

但是,如果我尝试多次呼叫会话。

test_employee.py

class TestEmployee:

    def test_create_employee_get(self, app, session):
        app.post('/login', data=dict(username='admin', password='12345'),
                 content_type='application/x-www-form-urlencoded', follow_redirects=True)

        response = app.get('/create')
        assert response.status_code == 200
        assert b'Create Employee' in response.data
        assert b'first name' in response.data
        assert b'Date of birth' in response.data
        assert b'sex' in response.data

    def test_create_employee_post(self, app, session):
        app.post('/login', data=dict(username='admin', password='12345'),
                 content_type='application/x-www-form-urlencoded', follow_redirects=True)

        response = app.post('/create', data=dict(
            first_name='Another', patronymic='Test', last_name='Just',
            position='Check', office='Москва', birthday='1990-01-01', sex='man'),
                            content_type='application/x-www-form-urlencoded', follow_redirects=True)
        assert response.status_code == 200

它不会在每个功能之后删除数据库。

____________________________________________________________________ ERROR at setup of TestEmployee.test_create_employee_get ____________________________________________________________________
[gw2] darwin -- Python 3.6.6 /Users/vladimir/UsetechTelegramBot/venv/bin/python
self = <sqlalchemy.engine.base.Connection object at 0x106737b70>
dialect = <sqlalchemy.dialects.postgresql.psycopg2.PGDialect_psycopg2 object at 0x10671ac50>
constructor = <bound method DefaultExecutionContext._init_compiled of <class 'sqlalchemy.dialects.postgresql.psycopg2.PGExecutionContext_psycopg2'>>
statement = 'INSERT INTO "user" (username, password_hash) VALUES (%(username)s, %(password_hash)s) RETURNING "user".id'
parameters = {'password_hash': 'pbkdf2:sha256:50000$Ek5K9otp$eea50131bd908bfb87427fe975f0f04e2e6607dd002919da77d1ad322a95078d', 'username': 'admin'}
args = (<sqlalchemy.dialects.postgresql.psycopg2.PGCompiler_psycopg2 object at 0x106737860>, [{'password_hash': 'pbkdf2:sha256:50000$Ek5K9otp$eea50131bd908bfb87427fe975f0f04e2e6607dd002919da77d1ad322a95078d', 'username': 'admin'}])
conn = <sqlalchemy.pool._ConnectionFairy object at 0x10677b4a8>
context = <sqlalchemy.dialects.postgresql.psycopg2.PGExecutionContext_psycopg2 object at 0x106737898>

    def _execute_context(self, dialect, constructor,
                         statement, parameters,
                         *args):
        """Create an :class:`.ExecutionContext` and execute, returning
            a :class:`.ResultProxy`."""

        try:
            try:
                conn = self.__connection
            except AttributeError:
                # escape "except AttributeError" before revalidating
                # to prevent misleading stacktraces in Py3K
                conn = None
            if conn is None:
                conn = self._revalidate_connection()

            context = constructor(dialect, self, conn, *args)
        except BaseException as e:
            self._handle_dbapi_exception(
                e,
                util.text_type(statement), parameters,
                None, None)

        if context.compiled:
            context.pre_exec()

        cursor, statement, parameters = context.cursor, \
            context.statement, \
            context.parameters

        if not context.executemany:
            parameters = parameters[0]

        if self._has_events or self.engine._has_events:
            for fn in self.dispatch.before_cursor_execute:
                statement, parameters = \
                    fn(self, cursor, statement, parameters,
                       context, context.executemany)

        if self._echo:
            self.engine.logger.info(statement)
            self.engine.logger.info(
                "%r",
                sql_util._repr_params(parameters, batches=10)
            )

        evt_handled = False
        try:
            if context.executemany:
                if self.dialect._has_events:
                    for fn in self.dialect.dispatch.do_executemany:
                        if fn(cursor, statement, parameters, context):
                            evt_handled = True
                            break
                if not evt_handled:
                    self.dialect.do_executemany(
                        cursor,
                        statement,
                        parameters,
                        context)
            elif not parameters and context.no_parameters:
                if self.dialect._has_events:
                    for fn in self.dialect.dispatch.do_execute_no_params:
                        if fn(cursor, statement, context):
                            evt_handled = True
                            break
                if not evt_handled:
                    self.dialect.do_execute_no_params(
                        cursor,
                        statement,
                        context)
            else:
                if self.dialect._has_events:
                    for fn in self.dialect.dispatch.do_execute:
                        if fn(cursor, statement, parameters, context):
                            evt_handled = True
                            break
                if not evt_handled:
                    self.dialect.do_execute(
                        cursor,
                        statement,
                        parameters,
>                       context)

venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1193: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <sqlalchemy.dialects.postgresql.psycopg2.PGDialect_psycopg2 object at 0x10671ac50>
cursor = <cursor object at 0x1065deed0; closed: -1>
statement = 'INSERT INTO "user" (username, password_hash) VALUES (%(username)s, %(password_hash)s) RETURNING "user".id'
parameters = {'password_hash': 'pbkdf2:sha256:50000$Ek5K9otp$eea50131bd908bfb87427fe975f0f04e2e6607dd002919da77d1ad322a95078d', 'username': 'admin'}
context = <sqlalchemy.dialects.postgresql.psycopg2.PGExecutionContext_psycopg2 object at 0x106737898>

    def do_execute(self, cursor, statement, parameters, context=None):
>       cursor.execute(statement, parameters)
E       psycopg2.IntegrityError: duplicate key value violates unique constraint "ix_user_username"
E       DETAIL:  Key (username)=(admin) already exists.

venv/lib/python3.6/site-packages/sqlalchemy/engine/default.py:509: IntegrityError

The above exception was the direct cause of the following exception:

app = <FlaskClient <Flask 'backend.auth'>>
request = <SubRequest 'db' for <Function 'test_create_employee_get'>>

    @pytest.fixture(scope='session')
    def db(app, request):
        # Create the database and the database table
        def teardown():
            _db.drop_all()

        _db.app = app
        _db.create_all()
        user = User(username='admin')
        user.set_password('12345')
        _db.session.add(user)
>       _db.session.commit()

tests/conftest.py:64: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
venv/lib/python3.6/site-packages/sqlalchemy/orm/scoping.py:153: in do
    return getattr(self.registry(), name)(*args, **kwargs)
venv/lib/python3.6/site-packages/sqlalchemy/orm/session.py:943: in commit
    self.transaction.commit()
venv/lib/python3.6/site-packages/sqlalchemy/orm/session.py:467: in commit
    self._prepare_impl()
venv/lib/python3.6/site-packages/sqlalchemy/orm/session.py:447: in _prepare_impl
    self.session.flush()
venv/lib/python3.6/site-packages/sqlalchemy/orm/session.py:2254: in flush
    self._flush(objects)
venv/lib/python3.6/site-packages/sqlalchemy/orm/session.py:2380: in _flush
    transaction.rollback(_capture_exception=True)
venv/lib/python3.6/site-packages/sqlalchemy/util/langhelpers.py:66: in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
venv/lib/python3.6/site-packages/sqlalchemy/util/compat.py:249: in reraise
    raise value
venv/lib/python3.6/site-packages/sqlalchemy/orm/session.py:2344: in _flush
    flush_context.execute()
venv/lib/python3.6/site-packages/sqlalchemy/orm/unitofwork.py:391: in execute
    rec.execute(self)
venv/lib/python3.6/site-packages/sqlalchemy/orm/unitofwork.py:556: in execute
    uow
venv/lib/python3.6/site-packages/sqlalchemy/orm/persistence.py:181: in save_obj
    mapper, table, insert)
venv/lib/python3.6/site-packages/sqlalchemy/orm/persistence.py:866: in _emit_insert_statements
    execute(statement, params)
venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py:948: in execute
    return meth(self, multiparams, params)
venv/lib/python3.6/site-packages/sqlalchemy/sql/elements.py:269: in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1060: in _execute_clauseelement
    compiled_sql, distilled_params
venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1200: in _execute_context
    context)
venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1413: in _handle_dbapi_exception
    exc_info
venv/lib/python3.6/site-packages/sqlalchemy/util/compat.py:265: in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=cause)
venv/lib/python3.6/site-packages/sqlalchemy/util/compat.py:248: in reraise
    raise value.with_traceback(tb)
venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1193: in _execute_context
    context)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <sqlalchemy.dialects.postgresql.psycopg2.PGDialect_psycopg2 object at 0x10671ac50>
cursor = <cursor object at 0x1065deed0; closed: -1>
statement = 'INSERT INTO "user" (username, password_hash) VALUES (%(username)s, %(password_hash)s) RETURNING "user".id'
parameters = {'password_hash': 'pbkdf2:sha256:50000$Ek5K9otp$eea50131bd908bfb87427fe975f0f04e2e6607dd002919da77d1ad322a95078d', 'username': 'admin'}
context = <sqlalchemy.dialects.postgresql.psycopg2.PGExecutionContext_psycopg2 object at 0x106737898>

    def do_execute(self, cursor, statement, parameters, context=None):
>       cursor.execute(statement, parameters)
E       sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) duplicate key value violates unique constraint "ix_user_username"
E       DETAIL:  Key (username)=(admin) already exists.
E        [SQL: 'INSERT INTO "user" (username, password_hash) VALUES (%(username)s, %(password_hash)s) RETURNING "user".id'] [parameters: {'username': 'admin', 'password_hash': 'pbkdf2:sha256:50000$Ek5K9otp$eea50131bd908bfb87427fe975f0f04e2e6607dd002919da77d1ad322a95078d'}] (Background on this error at: http://sqlalche.me/e/gkpj)

venv/lib/python3.6/site-packages/sqlalchemy/engine/default.py:509: IntegrityError

1 个答案:

答案 0 :(得分:1)

更改为此,一切正常。

@pytest.fixture(scope='session')
def db(app):

    _db.app = app
    _db.create_all()

    return _db