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