web-dev-qa-db-fra.com

Pytest: installer testclient et DB

J'essaie d'apprendre quelque chose sur le test de mon application de flacon. Pour ce faire, j'utilise pytest et sqlalchemy.

Je veux tester un modèle, qui livre du contenu SQL. Donc, à mon avis, j'ai besoin d'un testClient pour tester la route elle-même et d'un appareil de base de données pour gérer les éléments de base de données inclus dans la route.

Voici mon appareil:

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()

Et voici mon test de base:

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

L'exécution de mon test échoue avec:

=================================================================================================== 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 ============================================================================================

ce qui me dit: db.create_all () ne pas crée-t-il toutes les tables de ma base de données de test . Tout indice, qu'est-ce que je fais de mal ici?

Quelques informations supplémentaires:

  • utiliser sqlite pour le moment
  • le fichier de base de données lui-même est créé dans le système de fichiers avec 0 octet

Plus de débogage: J'ai suivi ce guide ici: https://xvrdm.github.io/2017/07/03/testing-flask-sqlalchemy-database-with-pytest/

c'est là que la chose devient étrange:

Lien d'en haut:

>>> 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

Que se passe-t-il dans mon projet?

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

Extrait de 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)
9
Dakkar

J'ai trouvé la solution.

@Dmitrybelyakov était assez proche:

l'importation du modèle était l'indice. 

Qu'est-ce qui ne marche pas?

from application.model import Order

Qu'est-ce qui fonctionne:

from application.model import *

Je ne sais pas exactement pourquoi il ne fonctionne pas d'importer un seul modèle, mais finalement, il fonctionne. Voici mon appareil complet:

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()
0
Dakkar

Vous devez utiliser flask-sqlalchemy, en coulisse, il utilise l’extension déclarative pour définir vos modèles.

En sous-classant une classe sqlalchemy déclarative base , sqlalchemy générera Table et mapper, les magasins d’informations de table nouvellement créés dans la Metadata obj. db.create_all()est en réalitémetadata.create_all(), ce qui ne créera que les tables stockées dans les métadonnées .

Par conséquent, avant d'essayer de créer une table avec metadata.create_all, vous devez d'abord stocker les informations de cette table dans le registre metadata, ce qui revient à définir une sous-classe de base déclarative. En python, cela signifie que le code de définition de votre classe doit être exécuté, ce qui à son tour, import et module, les classes définies.

5
georgexsh