Pytest: настройка testclient и бд

Я пытаюсь узнать что-то о тестировании моего флеш-приложения. Для этого я использую pytest и sqlalchemy.

Я хочу протестировать шаблон, который обеспечивает маршрутизацию некоторого содержимого SQL. Поэтому, на мой взгляд, мне нужен testClient для тестирования самого маршрута и приспособление БД для управления материалами БД, включенными в маршрут.

Вот мое приспособление:

import pytest
from config import TestingConfig
from application import create_app, db


# ###########################
# ## functional tests
# ###########################


@pytest.fixture(scope='module')
def test_client():
    app = create_app(TestingConfig)

    # Flask provides a way to test your application by exposing the Werkzeug 
    # test Client and handling the context locals for you.
    testing_client = app.test_client()

    with app.app_context():

        db.create_all()

        yield testing_client  # this is where the testing happens!

        db.drop_all()

И это мой основной тест:

def test_home_page(test_client):
    """
    GIVEN a Flask application
    WHEN the '/' page is requested (GET)
    THEN check the response is valid and contains rendered content
    """
    response = test_client.get('/')
    assert response.status_code == 200
    assert "SOME CONTENT" in response.data

Провести мой тест не удалось:

=================================================================================================== test session starts ===================================================================================================
platform linux -- Python 3.5.2, pytest-3.8.0, py-1.5.4, pluggy-0.7.1
rootdir: /home/dakkar/devzone/private/, inifile:
collected 2 items                                                                                                                                                                                                         

tests/test_main.py 
    SETUP    M test_client
        tests/test_main.py::test_home_page (fixtures used: test_client)F
        tests/test_main.py::test_valid_order_message (fixtures used: test_client).
    TEARDOWN M test_client

======================================================================================================== FAILURES =========================================================================================================
_____________________________________________________________________________________________________ test_home_page ______________________________________________________________________________________________________

self = <sqlalchemy.engine.base.Connection object at 0x7f1c3f29b630>, dialect = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x7f1c3f2c4ba8>
constructor = <bound method DefaultExecutionContext._init_compiled of <class 'sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext'>>
statement = 'SELECT sum("order".col2_count) AS orders_col2, sum("order".col1_count) AS orders_col1, count("order".id) AS orders_count \nFROM "order"', parameters = ()
args = (<sqlalchemy.dialects.sqlite.base.SQLiteCompiler object at 0x7f1c3f29b6d8>, [immutabledict({})]), conn = <sqlalchemy.pool._ConnectionFairy object at 0x7f1c3f29b550>
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x7f1c3f29b6a0>

    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.5/site-packages/sqlalchemy/engine/base.py:1193: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x7f1c3f2c4ba8>, cursor = <sqlite3.Cursor object at 0x7f1c3f2c2ce0>
statement = 'SELECT sum("order".col2_count) AS orders_col2, sum("order".col1_count) AS orders_col1, count("order".id) AS orders_count \nFROM "order"', parameters = ()
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x7f1c3f29b6a0>

    def do_execute(self, cursor, statement, parameters, context=None):
>       cursor.execute(statement, parameters)
E       sqlite3.OperationalError: no such table: order

venv/lib/python3.5/site-packages/sqlalchemy/engine/default.py:509: OperationalError

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

test_client = <FlaskClient <Flask 'application'>>

    def test_home_page(test_client):
        """
        GIVEN a Flask application
        WHEN the '/' page is requested (GET)
        THEN check the response is valid and contains rendered content
        """
>       response = test_client.get('/')

tests/test_main.py:7: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
venv/lib/python3.5/site-packages/werkzeug/test.py:830: in get
    return self.open(*args, **kw)
venv/lib/python3.5/site-packages/flask/testing.py:200: in open
    follow_redirects=follow_redirects
venv/lib/python3.5/site-packages/werkzeug/test.py:803: in open
    response = self.run_wsgi_app(environ, buffered=buffered)
venv/lib/python3.5/site-packages/werkzeug/test.py:716: in run_wsgi_app
    rv = run_wsgi_app(self.application, environ, buffered=buffered)
venv/lib/python3.5/site-packages/werkzeug/test.py:923: in run_wsgi_app
    app_rv = app(environ, start_response)
venv/lib/python3.5/site-packages/flask/app.py:2309: in __call__
    return self.wsgi_app(environ, start_response)
venv/lib/python3.5/site-packages/flask/app.py:2295: in wsgi_app
    response = self.handle_exception(e)
venv/lib/python3.5/site-packages/flask/app.py:1741: in handle_exception
    reraise(exc_type, exc_value, tb)
venv/lib/python3.5/site-packages/flask/_compat.py:35: in reraise
    raise value
venv/lib/python3.5/site-packages/flask/app.py:2292: in wsgi_app
    response = self.full_dispatch_request()
venv/lib/python3.5/site-packages/flask/app.py:1815: in full_dispatch_request
    rv = self.handle_user_exception(e)
venv/lib/python3.5/site-packages/flask/app.py:1718: in handle_user_exception
    reraise(exc_type, exc_value, tb)
venv/lib/python3.5/site-packages/flask/_compat.py:35: in reraise
    raise value
venv/lib/python3.5/site-packages/flask/app.py:1813: in full_dispatch_request
    rv = self.dispatch_request()
venv/lib/python3.5/site-packages/flask/app.py:1799: in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
application/main/routes.py:20: in index
    func.count(Order.id).label("orders_count")
venv/lib/python3.5/site-packages/sqlalchemy/orm/query.py:2947: in one
    ret = self.one_or_none()
venv/lib/python3.5/site-packages/sqlalchemy/orm/query.py:2917: in one_or_none
    ret = list(self)
venv/lib/python3.5/site-packages/sqlalchemy/orm/query.py:2988: in __iter__
    return self._execute_and_instances(context)
venv/lib/python3.5/site-packages/sqlalchemy/orm/query.py:3011: in _execute_and_instances
    result = conn.execute(querycontext.statement, self._params)
venv/lib/python3.5/site-packages/sqlalchemy/engine/base.py:948: in execute
    return meth(self, multiparams, params)
venv/lib/python3.5/site-packages/sqlalchemy/sql/elements.py:269: in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
venv/lib/python3.5/site-packages/sqlalchemy/engine/base.py:1060: in _execute_clauseelement
    compiled_sql, distilled_params
venv/lib/python3.5/site-packages/sqlalchemy/engine/base.py:1200: in _execute_context
    context)
venv/lib/python3.5/site-packages/sqlalchemy/engine/base.py:1413: in _handle_dbapi_exception
    exc_info
venv/lib/python3.5/site-packages/sqlalchemy/util/compat.py:265: in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=cause)
venv/lib/python3.5/site-packages/sqlalchemy/util/compat.py:248: in reraise
    raise value.with_traceback(tb)
venv/lib/python3.5/site-packages/sqlalchemy/engine/base.py:1193: in _execute_context
    context)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x7f1c3f2c4ba8>, cursor = <sqlite3.Cursor object at 0x7f1c3f2c2ce0>
statement = 'SELECT sum("order".col2_count) AS orders_col2, sum("order".col1_count) AS orders_col1, count("order".id) AS orders_count \nFROM "order"', parameters = ()
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x7f1c3f29b6a0>

    def do_execute(self, cursor, statement, parameters, context=None):
>       cursor.execute(statement, parameters)
E       sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: order [SQL: 'SELECT sum("order".col2_count) AS orders_col2, sum("order".col1_count) AS orders_col1, count("order".id) AS orders_count \nFROM "order"'] (Background on this error at: http://sqlalche.me/e/e3q8)

venv/lib/python3.5/site-packages/sqlalchemy/engine/default.py:509: OperationalError
=========================================================================================== 1 failed, 1 passed in 0.52 seconds ============================================================================================

который говорит мне: db.create_all () создает ли нет все таблицы в моей тестовой базе данных. Любой намек, что я здесь делаю не так?

Дополнительная информация:

  • использую sqlite в данный момент
  • сам файл базы данных создается в файловой системе с 0 байтом

Дополнительная отладка: Я следовал этому руководству здесь: https://xvrdm.github.io/2017/07/03/testing-flask-sqlalchemy-database-with-pytest/

вот где все стало странно:

Ссылка сверху:

>>> db.engine.table_names()  # Check the tables currently on the engine
[]                           # no table found
>>> db.create_all()          # Create the tables according to defined models
>>> db.engine.table_names()
['users']                    # Now table 'users' is found

Что происходит в моем проекте:

>>> db.engine.table_names()
[]
>>> db.create_all()
>>> db.engine.table_names()
[]
>>>

Фрагмент из models.py:

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()


class Order(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(120), index=True, unique=True)

не могли бы вы опубликовать полную трассировку стека?

rocksportrocker 10.09.2018 10:41

сделано. полная трассировка стека включена

Dakkar 10.09.2018 11:02

пробовали ли вы импортировать свои модели перед тем, как сделать create_all ()?

dmitrybelyakov 10.09.2018 11:13

их импорт в мой conftest.py этого не исправляет.

Dakkar 10.09.2018 11:44

Можете ли вы также опубликовать, как вы их импортируете?

mad_ 10.09.2018 14:55

в моем conftest.py: from application.models import Order

Dakkar 10.09.2018 23:52

Можете ли вы использовать операторы отладочной печати, чтобы узнать, используете ли вы тот же файл sqlite db, например. строка подключения? Вы также можете ввести дополнительную функцию тестирования для кода базы данных установки, отправив инструкции sql для подсчета строк и т. д. После завершения настройки.

rocksportrocker 11.09.2018 11:36

Я использую класс конфигурации для настройки своего приложения. Печать моей конфигурации дает мне: 'SQLALCHEMY_DATABASE_URI': 'sqlite: ////tmp/testing.db', что выглядит прекрасно. Подсчет строк или что-то в этом роде не сработает, потому что нет содержимого. create_dbs () должен ли я создавать модель без содержимого?

Dakkar 15.09.2018 18:23

Как заявлены ваши модели? Они наследуются от db.Model?

Mikhail Burshteyn 16.09.2018 12:24

добавлен фрагмент modely.py

Dakkar 16.09.2018 14:42
12
10
6 858
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я нашел решение.

@dmitrybelyakov был довольно близок к этому:

намек был импорт модели.

Что не работает:

from application.model import Order

Что работает:

from application.model import *

Я точно не знаю, почему не получается импортировать отдельную модель, но наконец она заработала. Вот мое полное приспособление:

import pytest
from config import TestingConfig
from application import create_app, db
from application.models import *


# ###########################
# ## functional tests
# ###########################


@pytest.fixture(scope='module')
def test_client():
    app = create_app(TestingConfig)

    # Flask provides a way to test your application by exposing the Werkzeug 
    # test Client and handling the context locals for you.
    testing_client = app.test_client()

    with app.app_context():
        db.create_all()

        yield testing_client  # this is where the testing happens!

        db.drop_all()

я тоже;) наверное / dev / null

Dakkar 23.09.2018 01:59

Вы должны использовать flask-sqlalchemy, за кулисами, он использует декларативное расширение для определения ваших моделей.

Создавая подклассы класса sqlalchemy декларативная база, sqlalchemy сгенерирует для вас Table и mapper, вновь созданные данные таблицы хранятся в соответствующем объекте Metadata. db.create_all()на самом делеmetadata.create_all(), который будет только создавать таблицы, хранящиеся в метаданных.

Следовательно, прежде чем вы попытаетесь создать таблицу с помощью metadata.create_all, вы должны сначала сохранить информацию этой таблицы в реестре metadata, что равносильно определению декларативного базового подкласса. В python это означает выполнение кода определения класса, который, в свою очередь, importmodule определенных классов.

Другие вопросы по теме