Testing the plugin

Tests live under tests/, mirroring the source tree 1:1 — every module in stratified_packager/ and scripts/ has a test_*.py at the same relative path. Tests that need a running QGIS are marked @pytest.mark.qgis and guard their imports with pytest.importorskip("qgis"), so they skip cleanly where QGIS is unavailable.

The test stack (pytest, pytest-cov, pytest-qgis, pytest-qt, hypothesis) is managed with uv and configured under [tool.pytest] / [tool.coverage] in pyproject.toml. Routine runs go through just (which shells out via uv run):

# all tests, with coverage (--cov is on by default)
just test

# a single module, or a single test
just test tests/stratified_packager/test___about__.py
just test tests/stratified_packager/test___about__.py::test_version_semver

# select by keyword
just test -k pattern

Coverage

Coverage is collected by default (--cov is configured under [tool.pytest] in pyproject.toml) and an HTML report is written to htmlcov/. Browse it with:

just serve-cov [PORT]    # serve htmlcov/ (default port 8080)
just browse-cov [PORT]   # open http://localhost:PORT

Aim for ≥ 80% coverage overall and ≥ 90% on core plugin logic.

Full local gate

just check (alias just ci) runs the whole gate — all QA checks (just qa) followed by the test suite. Run it before pushing.

PyQt5 / PyQt6

QGIS wraps whichever Qt binding the host ships — PyQt6 on QGIS 4.0+, PyQt5 on the 3.x LTRs — behind qgis.PyQt. The CI matrix runs the suite on both (PyQt5: QGIS 3.40/3.44; PyQt6: QGIS 4.0+), so a test that passes locally must pass under either binding.

Write tests against behaviour (return values, exceptions, side effects) through qgis.PyQt, not against a specific binding’s import path or enum spelling. Let the callable resolve its own try:/except ImportError: fallback; replicate that fallback in the test only when the divergence can’t be hidden. When a behaviour exists only on the newer version, skip the test on older ones with pytest.mark.skipif (see skip_if_no_unaccent in test_i18n.py) rather than forking the assertion.

Running without QGIS

pytest-qgis and pytest-qt import qgis / PyQt6 at load time, so in an environment without a QGIS/Qt runtime they must be disabled or they fail on autoload. Disable both and the QGIS-marked modules skip via importorskip:

just test -p no:pytest_qgis -p no:pytest-qt

Leaving pytest-qgis enabled when QGIS is missing makes it error loudly on autoload — that is intentional, so an absent or broken QGIS is never silently skipped.