Testing¶
Piccolo provides a few tools to make testing easier.
Test runner¶
Piccolo ships with a handy command for running your unit tests using pytest. See the tester app.
You can put your test files anywhere you like, but a good place is in a tests
folder within your Piccolo app. The test files should be named like
test_*. py
or *_test.py
for pytest to recognise them.
Model Builder¶
When writing unit tests, it’s usually required to have some data seeded into
the database. You can build and save the records manually or use
ModelBuilder
to generate
random records for you.
This way you can randomize the fields you don’t care about and specify
important fields explicitly and reduce the amount of manual work required.
ModelBuilder
currently supports all Piccolo column types and features.
Let’s say we have the following schema:
from piccolo.columns import ForeignKey, Varchar
class Manager(Table):
name = Varchar(length=50)
class Band(Table):
name = Varchar(length=50)
manager = ForeignKey(Manager, null=True)
You can build a random Band
which will also build and save a random
Manager
:
from piccolo.testing.model_builder import ModelBuilder
# Band instance with random values persisted:
band = await ModelBuilder.build(Band)
Note
ModelBuilder.build(Band)
persists the record into the database by default.
You can also run it synchronously if you prefer:
manager = ModelBuilder.build_sync(Manager)
To specify any attribute, pass the defaults
dictionary to the build
method:
manager = ModelBuilder.build(Manager)
# Using table columns:
band = await ModelBuilder.build(
Band,
defaults={Band.name: "Guido", Band.manager: manager}
)
# Or using strings as keys:
band = await ModelBuilder.build(
Band,
defaults={"name": "Guido", "manager": manager}
)
To build objects without persisting them into the database:
band = await ModelBuilder.build(Band, persist=False)
To build objects with minimal attributes, leaving nullable fields empty:
# Leaves manager empty:
band = await ModelBuilder.build(Band, minimal=True)
Creating the test schema¶
When running your unit tests, you usually start with a blank test database, create the tables, and then install test data.
To create the tables, there are a few different approaches you can take. Here
we use create_db_tables_sync
and
drop_db_tables_sync
.
Note
The async equivalents are create_db_tables
and drop_db_tables
.
from unittest import TestCase
from piccolo.table import create_db_tables_sync, drop_db_tables_sync
from piccolo.conf.apps import Finder
TABLES = Finder().get_table_classes()
class TestApp(TestCase):
def setUp(self):
create_db_tables_sync(*TABLES)
def tearDown(self):
drop_db_tables_sync(*TABLES)
def test_app(self):
# Do some testing ...
pass
Alternatively, you can run the migrations to setup the schema if you prefer:
from unittest import TestCase
from piccolo.apps.migrations.commands.backwards import run_backwards
from piccolo.apps.migrations.commands.forwards import run_forwards
from piccolo.utils.sync import run_sync
class TestApp(TestCase):
def setUp(self):
run_sync(run_forwards("all"))
def tearDown(self):
run_sync(run_backwards("all", auto_agree=True))
def test_app(self):
# Do some testing ...
pass
Testing async code¶
There are a few options for testing async code using pytest.
You can either call any async code using Piccolo’s run_sync
utility:
from piccolo.utils.sync import run_sync
async def get_data():
...
def test_get_data():
rows = run_sync(get_data())
assert len(rows) == 1
Alternatively, you can make your tests natively async.
If you prefer using pytest’s function based tests, then take a look at
pytest-asyncio. Simply
install it using pip install pytest-asyncio
, then you can then write tests
like this:
async def test_select():
rows = await MyTable.select()
assert len(rows) == 1
If you prefer class based tests, and are using Python 3.8 or above, then have
a look at IsolatedAsyncioTestCase
from Python’s standard library. You can then write tests like this:
from unittest import IsolatedAsyncioTestCase
class MyTest(IsolatedAsyncioTestCase):
async def test_select(self):
rows = await MyTable.select()
assert len(rows) == 1