Testing
just check is the complete offline pre-PR gate. It includes formatting,
linting, types, tests, generated documentation, and saved notebook validation.
Keep real network access out of this gate. Scientific decoder fixtures and
filesystem workflows belong here when they are deterministic and fast.
Levels and responsibilities
Levels describe dependencies, not quality or importance. Run a regression at the lowest level that reproduces the behavior; preserve real decoder tests when mocks would miss format or alignment errors.
| Level | Scope here | Default home | Policy |
|---|---|---|---|
| L0 | Pure in-memory logic | tests/unit/ |
Every PR |
| L1 | Components with mocked transport | tests/adapters/, tests/protocols/ |
Every PR |
| L2 | Local files, SDK/CLI workflows, real scientific decoding | tests/component/ |
Every PR |
| L3 | Controlled service deployment | None: this SDK has no test deployment | Reserved |
| L4 | Production/upstream compatibility | tests/live/ |
Scheduled/manual |
L4 is a local adaptation for an SDK using public upstream services. A live NOAA probe is not a controlled staging deployment. Installed-wheel smoke tests use local fixtures (L2); resolving dependencies during environment setup is separate from the offline behavior being tested. Saved notebook validation is offline; executing the examples against public providers is live.
Collection assigns a level from the directory unless an explicit l0, l1,
l2, l3, or l4 marker overrides it. Declare exactly one level. Mark tests
that use local files @pytest.mark.l2 even inside adapter/transport modules;
a module using file fixtures throughout may declare pytestmark = pytest.mark.l2.
component modules conservatively use L2 for their combined local workflows.
Use pandas, radar, and netcdf markers for reader test groups. These select
coverage; they do not install dependencies. Tests still check optional imports,
and core-only CI must remain valid. Contract tests describe a purpose, not a
separate execution level.
Selecting tests
just test: all offline tests; live cases are skipped.just test -m l0: pure tests.just test -m 'l1 or l2': offline component and local functional tests.just test tests/adapters/test_goes.py: one adapter's offline scenarios.just test -m radar: reader tests, with the radar extra installed.just test-live: explicitly enable and select upstream tests.just test-live tests/live/test_goes_live.py: one upstream dataset.
just test-integration, --run-integration, and the integration marker remain
compatibility aliases for live execution. A live marker is required to permit
network connections, and the CLI opt-in is required to run those cases. All
other tests are guarded against real socket connections and DNS resolution.
This guard applies to the pytest process, not arbitrary subprocesses.
Adding and maintaining coverage
Keep source-specific query rules, pagination semantics, and response fixtures
with the adapter. Shared protocol behavior belongs under tests/protocols/.
Cache integrity, manifest restoration, and CLI error behavior belong with the
responsible component rather than a cross-cutting regression catch-all.
Prefer small fixtures, injected transports, meaningful failure assertions, and
parameter tables for equivalent cases. Test count is not a target. Measure
runtime and investigate failures; high line coverage does not establish correct
pagination, resource ownership, or scientific semantics. Keep just check
useful as providers grow instead of duplicating the entire core suite per adapter.
CI reports and live isolation
CI runs repository-wide static/document checks once, then types and all offline behavior tests in each supported Python/dependency profile. Core-only and each optional extra remain separate environments. Built-wheel checks on Linux, macOS, and Windows remain required; publishing still promotes the artifact from successful CI for the exact commit.
just check-static and just check-tests expose those same parts locally.
just check-tests --junitxml=reports/junit.xml --cov-report=json:reports/coverage.json
retains machine-readable results. Coverage includes branches; it is diagnostic,
not a target for adding superficial tests. CI retains JUnit, coverage, and timing
reports for 14 days under uniquely named artifacts for each profile.
The scheduled/manual Integration workflow discovers jobs from live test modules and example notebooks. Each dataset and example runs independently with fail-fast disabled and a job timeout. GOES live decoding explicitly uses the NetCDF extra; a core-only run reports that decoder test as skipped rather than silently omitting its assertions. The workflow also tests minimum direct runtime dependencies on Python 3.11 in a fresh environment, retaining the resolved versions. This probes core lower bounds with current compatible transitive dependencies; it does not claim minimum-version coverage for optional scientific stacks.
just run-notebooks --notebook examples/goes-imagery/example.ipynb selects one
example (repeat the flag for more). Executions retain partial notebooks, error
traces, timings, and summary.json in ignored reports/notebooks/; use
--output-dir to choose another report directory. A failed example does not
prevent later selected examples from running. --write refreshes the selected
committed notebooks only if every selected example succeeds.
Documentation is checked once in the static job: just check-docs checks generated
catalogs and saved notebooks, then builds the site with strict internal link and
anchor validation. See maintaining documentation.
The v0.10.0 first-use review records a bounded live walkthrough from a fresh PyPI installation, its documentation fixes, and its limits. It is historical review evidence, not a current upstream-health check.
Focused hosted checks
Weekly Integration runs retain the full live, notebook, and minimum-dependency suites. Manual runs can select a scope and one target; an empty target runs the whole selected scope. Examples from a checkout with the GitHub CLI:
gh workflow run integration.yml -f scope=live -f target=coops
gh workflow run integration.yml -f scope=notebooks -f target=sst-analysis
gh workflow run integration.yml -f scope=minimum
gh workflow run integration.yml -f scope=all
A live target is the test module stem without test_ and _live; a notebook
target is its example folder name. Full repository-relative paths also work.
Unknown or ambiguous targets fail before starting jobs. The all and minimum
scopes reject a target. Use --ref BRANCH to test a workflow change before merging.
Each test job writes counts, failures, skips, and execution time to the Actions
run summary; notebook jobs report status and duration. Reports missing after a
setup failure are identified explicitly, and full diagnostics remain in artifacts.