Testing#
Similar to the actual library source itself, the tests are subdivided primarily by “feature” (i.e. schema/view/grant/etc).
There are a couple of key organizational factors that should become apparent, looking at the tests’ folder structure:
tests/
examples/
test_view_create_pg/
test_view_create_mysql/
...
view/
test_alembic.py
test_materialized.py
...
...
Alembic tests#
Generally, alembic tests are much slower and more difficult to debug than regular tests. Therefore they should be limited as much as possible to the things required to get proper code-coverage of a feature within alembic.
When testing variants of a feature where the only difference would be equally
well tested when running a metadata.create_all()
call, you’re better off
avoiding alembic-based tests and using the “SQLAlchemy tests” examples below.
Now, to the details:
First, examples/
. Due to the way alembic works internally, in order to test
the alembic integration, we need to have a whole “fake” alembic migrations
history in a standalone folder, so that we can see whether a given feature works
end-to-end once running inside alembic.
Therefore, each folder inside the examples/
folder is a standalone pseudo
“project” with models, migrations, and tests. The tests for each pseudo-project
invoke pytest using pytest-alembic, in
order to run the alembic migrations against a real database.
These examples are invoked using Pytest’s pytester
fixture, which automates
the calling of the examples’ pytest inline or in a subprocess.
All of the alembic/examples-based tests for a given feature live in
tests/<feature>/test_alembic.py
:
import pytest
from tests.utilities import successful_test_run
@pytest.mark.alembic
def test_view_create_pg(pytester):
successful_test_run(pytester, count=1)
You’ll notice the name of the test must exactly match the name of the
examples/
folder’s name.
For organization’s sake, examples’ names should look like
test_<feature>_<test-purpose>_<dialect>/
.
SQLAlchemy tests#
By contrast to the alembic tests, they dont need a special way to be invoked.
One can just create a new tests/<feature>/test_<what-am-i-testing>.py
and be
done.
What still may be different relative to most projects though, is that most features of this library operate on a SQLAlchemy model-base/MetaData. So testing mulitple different scenarios within a singlar test file is frequently more challenging than it’s worth.
Therefore you’ll find that most test files contain exactly 1, or a couple of test. If a test file has more than one test, it’s frequently to test the same “thing” against the different dialect options.
For example:
import sqlalchemy
from pytest_mock_resources import create_postgres_fixture, create_sqlite_fixture
from sqlalchemy_declarative_extensions import (
Schemas,
declarative_database,
register_sqlalchemy_events,
)
from sqlalchemy_declarative_extensions.sqlalchemy import declarative_base
_Base = declarative_base()
@declarative_database
class Base(_Base): # type: ignore
__abstract__ = True
schemas = Schemas().are("fooschema")
class Foo(Base):
__tablename__ = "foo"
__table_args__ = {"schema": "fooschema"}
id = sqlalchemy.Column(sqlalchemy.types.Integer(), primary_key=True)
register_sqlalchemy_events(Base.metadata, schemas=True)
pg = create_postgres_fixture(scope="function", engine_kwargs={"echo": True})
sqlite = create_sqlite_fixture()
def test_createall_schema_pg(pg):
Base.metadata.create_all(bind=pg)
with pg.connect() as conn:
result = conn.execute(Foo.__table__.select()).fetchall()
assert result == []
def test_createall_schema_sqlite(sqlite):
Base.metadata.create_all(bind=sqlite, checkfirst=False)
with sqlite.connect() as conn:
result = conn.execute(Foo.__table__.select()).fetchall()
assert result == []