Ich versuche, etwas über das Testen meiner Kolben-App zu lernen. Um das zu tun, verwende ich Pytest und sqlalchemy.
Ich möchte eine Vorlage testen, deren Übergabe einige SQL-Inhalte weiterleitet. Meiner Meinung nach brauche ich einen testClient zum Testen der Route selbst und ein DB-Gerät zur Verwaltung des in der Route enthaltenen DB-Materials.
Hier ist mein Fixture:
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()
Und das ist mein grundlegender Test:
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
Das Ausführen meines Tests schlägt fehl mit:
=================================================================================================== 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 ============================================================================================
was sagt mir: db.create_all () erstellt nicht alle Tabellen in meiner Testdatenbank . Gibt es einen Hinweis, was mache ich hier falsch?
Einige zusätzliche Informationen:
Weitere Informationen zum Debuggen: Ich bin diesem Handbuch hier gefolgt: https://xvrdm.github.io/2017/07/03/testing-flask-sqlalchemy-database-with-pytest/
hier wird etwas seltsam:
Link von oben:
>>> 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
Was passiert in meinem Projekt:
>>> db.engine.table_names()
[]
>>> db.create_all()
>>> db.engine.table_names()
[]
>>>
Ausschnitt aus 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)
Ich habe die Lösung gefunden.
@dmitrybelyakov war ziemlich nah dran:
der Import des Modells war der Hinweis.
Was funktioniert nicht:
from application.model import Order
Was funktioniert?
from application.model import *
Ich weiß nicht genau, warum es nicht funktioniert, ein einzelnes Modell zu importieren, aber schließlich läuft es. Hier mein komplettes Fixture:
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()
Sie müssen Flask-sqlalchemy verwenden. Hinter den Kulissen verwendet es die deklarative Erweiterung, um Ihre Modelle zu definieren.
Durch die Unterklasse einer sqlalchemy deklarativen Basis Klasse generiert sqlalchemy für Sie Table
und mapper
, neu erstellte Tabelleninformationsspeicher im entsprechenden Metadata
obj. db.create_all()
ist eigentlichmetadata.create_all()
, wodurch nur Tabellen erstellt werden, die in den Metadaten gespeichert sind.
Bevor Sie versuchen, eine Tabelle mit metadata.create_all
zu erstellen, müssen Sie zuerst die Informationen dieser Tabelle in der Registrierung metadata
speichern. Dies ist gleich, um eine deklarative Basisunterklasse zu definieren. In Python bedeutet dies, dass Ihr Klassendefinitionscode ausgeführt wird, der wiederum import
die module
der definierten Klassen ist.