Testing
Abacus uses pytest for automated tests, plus local verification scripts for the broader confidence checks that glue linting, smoke paths, and packaging together. The expected workflow is to run targeted tests while you iterate and then run the wider local verification commands before you finish substantial work.
Test Layout
| Path | What it covers |
|---|---|
tests/test_*.py |
Shared infrastructure such as model IO, paths, package identity, and root-level helpers |
tests/mmm/ |
MMM behaviour at the public surface |
tests/mmm/models/ |
Extracted panel implementation seams |
tests/mmm/components/ |
Adstock and saturation component behaviour |
tests/mmm/plotting/ |
Static plotting helpers and theme/layout behaviour |
tests/mmm/optimization/ |
Budget optimisation logic and wrappers |
tests/mmm/diagnostics/ |
Structured diagnostics compute |
tests/mmm/summarization/ |
Summary/export helpers |
When you change a specific module seam, add or update tests in the matching test area instead of only asserting through a broad end-to-end test.
Core Commands
Fast targeted runs
Use targeted runs first. They are faster to interpret and make regressions easier to localise.
Whole-suite pytest
This installs the test extras and runs pytest with the local runtime defaults
from the Makefile.
Local verification
The Makefile defines the command graph:
| Target | Checks run |
|---|---|
verify_local |
check_format, then check_lint, then test |
check_format |
Install lint extras; Ruff format check on abacus tests scripts runme.py |
check_lint |
Install lint extras; Ruff check on the same paths; configured MyPy |
test |
Install test extras; the full configured pytest suite with local runtime defaults |
smoke_mmm |
Run the full runme.py --demo timeseries workflow with unchanged demo sampling budgets |
verify_package |
Install the build tool; build and clean-install the wheel under both reviewed dependency profiles; check requirements, imports, fitting, prediction and persistence |
verify_local_all |
verify_local, then verify_package |
verify_local does not include an explicit byte-compilation step or the
separate smoke_mmm target. Run make smoke_mmm when you intend to
verify execution of the full demo. For a small execution check, use the
bounded software smoke,
which explicitly skips holdout validation. MyPy checks the files selected in
pyproject.toml, not the whole package. Lint targets do not check ignored scratch scripts in sandbox/.
Inspect the current command graph without executing it:
Pre-commit static checks
With the lint extras installed in the active environment, run:
The local hook configuration runs Ruff format
checking, Ruff lint and the configured MyPy scope. It uses the active
python3 environment without downloading hook environments. Each hook checks
its full configured scope on every invocation, even when only documentation
changed. The hooks do not rewrite files.
Run pytest separately for test coverage. These static hooks do not replace
make verify_local or packaging checks. Manual invocation does not install a
Git hook; use python3 -m pre_commit install only if you want automatic checks
on local commits.
Packaging smoke
Run this when any of the following changed:
- packaging metadata in
pyproject.toml - import surfaces or compatibility facades
- bundled assets under
abacus/ - install-time behaviour or README/package artefacts
Runtime Environment
Some test paths need writable cache directories. The recommended defaults are:
The Makefile applies these defaults to test and smoke_mmm. Direct pytest
commands use your current environment; export them explicitly when needed.
The package verifier sets its own temporary PyTensor cache and Python linker.
Special Cases
Plotting tests
Prefer tests that inspect stable properties such as axes, labels, colours, sizes, rcParams, and return types. Avoid brittle pixel-perfect assertions.
For a focused plot run, you can disable Numba JIT explicitly:
Save/load and compatibility work
If you change model serialisation, identity strings, or import compatibility, add tests that prove older saved data or old import paths still work where that compatibility is expected.
Packaging and bundled assets
If you add or move package data, use make verify_package so the change is
checked against an installed wheel rather than only the editable repo checkout.
See dependency verification profiles
for their interpreter/platform scope and lower-bound limits. Passing tests and
installation smoke checks does not establish convergence, statistical validity
or causal identification.
What to Run Before You Finish
Small, localised change
- Targeted
pytest - Targeted
ruff check
Moderate code change
- Targeted
pytest make check_lintmake smoke_mmm
Broad or risky change
make verify_localmake verify_packageif packaging or bundled assets changed
Writing Good Tests
- Test observable behaviour, not implementation noise.
- Keep fixtures close to the layer you are testing.
- Prefer additive compatibility tests when preserving old behaviour.
- Use small synthetic data where possible.
- For plotting and serialisation, assert the stable contract rather than fragile internals.