Testing Overview¶
This page describes how CALISTA configures and runs tests with pytest: where configuration lives, how the test suite is organized, which markers we use, and how to select tests efficiently.
Pytest Configuration¶
All pytest options are defined in pyproject.toml under the [tool.pytest.ini_options] section.
We deliberately avoid pytest.ini, tox.ini, or setup.cfg, since their presence takes precedence and would cause pytest to ignore the pyproject.toml block.
Keeping all configuration in pyproject.toml ensures tool settings are centralized, Git-tracked, and consistent with the rest of the project (ruff, mypy, etc.).
Test Suite Organization¶
-
unit/Isolated, fast checks of a single module/class/function. No real I/O. Prefer fakes over mocks at boundaries. Deterministic assertions. -
integration/Real interactions with external systems (database, filestore, network). Use realistic setup/teardown; minimize mocking. -
functional/User-visible flows at the system boundary (e.g., CLI viaclick.CliRunner). Treat the system as a black box; assert outputs/side-effects, not internals. -
contract/Shared behavior/invariants enforced across multiple implementations/backends. Parametrize implementations via fixtures. Assert the public contract only. -
helpers/Shared utilities for tests (matchers, builders, small helpers). No tests live here. -
Property-based tests Live alongside the layer they exercise and opt-in with
@pytest.mark.property.
Testing & Markers¶
CALISTA uses a small, stable set of pytest markers. Base suite marks are applied by folder during collection; cross-cutting marks are opt-in.
Marker taxonomy¶
| Marker | Meaning | Default application (by folder) |
|---|---|---|
unit |
Fast, isolated checks of a single module/class/function (no real I/O). | tests/unit/ |
integration |
Interactions with external systems (DB, filestore, network, etc.). | tests/integration/ |
functional |
User-visible flows at the boundary (e.g., CLI via CliRunner). |
tests/functional/ |
contract |
Invariants shared across implementations/backends. | tests/contract/ |
property |
Property-based style (Hypothesis or similar). | Opt-in per module/test |
slow |
Slow tests | Opt-in per module/test |
Selection recipes¶
# Fast local cycle (pure unit, exclude slow)
pytest -m "unit and not slow"
# Everything except slow
pytest -m "not slow"
# Only integration
pytest -m integration
# Functional OR contract
pytest -m "functional or contract"
# All property-based tests (across suites)
pytest -m property
Applying marks¶
- Base suite marks (
unit,integration,functional,contract) are added by folder during collection; no manual tagging needed. - Cross-cutting marks (
property,slow) are opt-in at module scope (e.g.,pytestmark = [pytest.mark.slow]).
Registration¶
[tool.pytest.ini_options]
markers = [
"unit: Unit tests (fast, isolated, no real I/O)",
"integration: Integration tests (real external systems)",
"functional: Functional tests at the boundary (CLI/API flows)",
"contract: Contract/invariant tests across implementations",
"property: Property-based tests",
"slow: Slow tests"
]
Consistency rules¶
- Put tests where they conceptually belong; the folder will auto-apply the base mark.
- If a test needs real I/O, move it to
tests/integration/rather than keeping it inunit/. - Keep the marker set small and stable; add new marks only when they carry durable meaning.
Integration prerequisites¶
- Docker is installed and running (Docker Desktop, Colima, Rancher Desktop, or Podman with a Docker-compatible socket).
- No environment variables are required to run tests; Testcontainers will provision Postgres automatically.
- Ensure the machine can pull container images (network access).
CI usage¶
- Pull requests: run the entire suite, including tests marked
slow. - Future adjustment (not active): if the number of
slowtests grows significantly, we may switch PRs to-m "not slow"and runslowin scheduled jobs.