Skip to the content.

Testing

uv sync --extra dev
uv run pytest                      # everything
uv run pytest -m "not integration" # fast: no environment building, ~40s
uv run pytest -m integration       # compatibility across anndata releases

CI runs the unit suite on Python 3.12 and 3.13 with a 90% coverage floor, and the compatibility suite as a separate job.

How the suite is organised

File What it covers
test_elements.py The element layer — encodings, string dtypes, readers and writers — run against HDF5, Zarr v2 and Zarr v3
test_storage.py Backend detection, copying, Zarr versions, consolidated metadata
test_formats.py .npy, .mtx, image and JSON export/import, including their failure modes
test_validate.py Dimension validation for every axis-bearing path, including raw/
test_invariants.py Relationships that must hold for any data: subsetting everything is the identity, split partitions exactly, concat undoes split
test_anndata_roundtrip.py anndata writes the fixtures, reads back our output
test_anndata_versions.py Compatibility with six real anndata releases (see below)
test_commands_phase2.py, test_commands_coverage.py, test_cli.py Command surfaces and error paths
test_subset.py, test_export.py, test_import.py, test_info_read.py, test_zarr.py, test_query.py Per-feature unit tests

Writing a test

Use the new_store fixture to get a store on each backend in turn — it is parametrised over h5ad, zarr2 and zarr3, so one test body covers all three:

def test_something(new_store):
    path, opener = new_store()
    with opener("a") as root:
        ew.write_string_array(root["uns"], "s", ["a", "b"])
    with opener("r") as root:          # reopen: Zarr rewrites its index on close
        assert read_str_all(root["uns"]["s"]) == ["a", "b"]

For CLI surfaces, use the module-level CliRunner and assert on result.stdout + (result.stderr or ""), since status goes to stderr. Strip ANSI before matching message text — Rich also wraps long lines, so collapse whitespace.

Compatibility testing against real anndata releases

test_anndata_versions.py does not trust this repo’s idea of the format. For each release below it builds an environment with uv, writes a reference store with that exact anndata, and then checks that the CLI reads it, and that what the CLI writes can be reopened by that same release.

Release Interpreter Pins Why it is in the list
0.8.0 3.11 pandas<2, numpy<2, zarr<3 Introduced encoding-type/encoding-version
0.9.2 3.11 pandas<2, numpy<2, zarr<3  
0.10.9 3.12 pandas<3, numpy<2, zarr<3  
0.11.4 3.12 pandas<3, zarr<3 The index became a nullable-string-array group
0.12.2 3.12 pandas<3, zarr>=3 Zarr v3
0.13.3 3.12 zarr>=3 Zarr v3 by default

The pins matter: a modern pandas makes string columns a type the older releases cannot write, and pandas 1.x has no wheels for Python 3.12. To add a release, append to RELEASES in tests/reference_stores.py.

First run downloads those environments; afterwards uv serves them from cache and the suite takes about a minute. To skip it without the marker:

ADATA_SKIP_VERSION_FIXTURES=1 uv run pytest

Coverage

uv run pytest -m "not integration" --cov=adata --cov-report=term-missing

The floor is 90%. What remains uncovered is mostly defensive except branches around backend calls that do not fail in practice.