Tests

The test suite lives in the tests/ directory at the repository root. Tests use pytest.

Running tests

# Run the full suite
pytest

# Run a specific test file
pytest tests/test_pipelines.py

# Run a specific test class or function
pytest tests/test_pipelines.py::TestFerreUtilsHelpers::test_get_ferre_spectrum_name

# With coverage
pytest --cov=astra

In-memory database

Tests that interact with the database set ASTRA_DATABASE_PATH to :memory: before importing any Astra modules. This is done at the top of each test file:

import os
os.environ["ASTRA_DATABASE_PATH"] = ":memory:"

This ensures that every test run starts with a fresh, empty SQLite database. No external database server is needed.

When a test needs tables to exist, it creates them explicitly:

from astra.models.source import Source
from astra.models.spectrum import Spectrum
from astra.models.pipeline import PipelineOutputMixin

class Dummy(PipelineOutputMixin):
    pass

for model in (Source, Spectrum, Dummy):
    model.create_table()

Test structure

The test files cover different aspects of the codebase:

File

What it tests

test_models.py

ORM model behaviour: unique constraints, upsert logic, version handling.

test_pipelines.py

Pipeline utility functions (FERRE helpers, ASPCAP helpers, spectral resampling, The Payne utilities, SLAM wavelength tools, SnowWhite helpers). Uses _load_module_from_file to load pipeline modules directly, bypassing heavy __init__.py imports.

test_fields.py

Custom Peewee field types (BitField, etc.).

test_glossary.py

Glossary consistency checks.

test_spectrum_mixin.py

SpectrumMixin properties and methods.

test_model_flags.py

BitField flag definitions on pipeline models.

test_model_paths.py

File path generation on spectrum models.

test_pipeline_from_spectrum.py

PipelineOutputMixin.from_spectrum() behaviour.

test_task_query_builder.py

generate_queries_for_task query construction.

test_utils.py

Utility functions (version encoding, path expansion, etc.).

test_base_helpers.py

BaseModel helper methods.

Loading modules without side effects

Some pipeline modules have heavy imports (TensorFlow, large data files) or trigger database connections through their __init__.py. The test suite avoids this by loading individual .py files directly using importlib:

import importlib.util

def _load_module_from_file(name, path):
    """Load a Python module directly from a file path."""
    spec = importlib.util.spec_from_file_location(name, path)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod

# Load just the utils module from a pipeline, skipping __init__.py
mod = _load_module_from_file(
    "aspcap_utils",
    os.path.join(_SRC, "astra", "pipelines", "aspcap", "utils.py"),
)

This pattern is used extensively in test_pipelines.py to test pipeline helper functions in isolation.

Writing new tests

When adding a test for a new pipeline or model:

  1. Set os.environ["ASTRA_DATABASE_PATH"] = ":memory:" at the top of the file, before any astra imports.

  2. Create any needed tables in a fixture or at the start of the test.

  3. Use _load_module_from_file if you only need to test utility functions and want to avoid heavy dependencies.

  4. For integration tests that exercise the @task decorator, create a dummy pipeline model, define a small @task function, and pass a handful of test spectra through it (see test_pipeline_replace_on_conflict in test_models.py for an example).