Testing & Development#
This guide covers testing, documentation building, and code style for NeuRodent.
Running Tests#
Run the test suite with pytest:
make test
# or: uv run pytest
Run with coverage:
uv run pytest --cov=neurodent
Run integration tests only:
uv run pytest tests/integration/ -v -m integration
Pipeline Testing Strategy#
The Snakemake pipeline is validated at three levels, all integrated into CI/CD via GitHub Actions:
Component-level integration tests (pytest) These tests exercise the core pipeline building blocks —
FileDiscoverer,AnimalOrganizer, WAR generation, plotters, and FDSAR — against a tiny synthetic NWB dataset generated on the fly. They run as part of the normaluv run pytestinvocation and are the fastest feedback loop.Snakemake DAG dry-run (pytest + subprocess)
TestSnakemakeDryRunintests/integration/test_snakemake_flow.pyinvokessnakemake --dryrunas a subprocess for both theexampleandmini_realdatasets. This validates the Snakefile, config files, sample JSONs, wildcard resolution, and rule definitions without processing any data.Snakemake real run (GitHub Actions, mini_real) The
test-build-docs.ymlworkflow runs a real full-pipelinesnakemakeexecution on the committed mini_real dataset. This validates end-to-end processing (data loading, discovery, analysis, filtering, figures) on every push/PR. Theexampledataset uses a DAG dry-run since its synthetic NWB data is generated at test-fixture time, not committed to the repo.
# Quick: validate DAG only (seconds)
NEURODENT_DATASET=example uv run snakemake --dryrun
# Real run: execute full pipeline on committed mini data
NEURODENT_DATASET=mini_real uv run snakemake --cores all
# Component integration tests (seconds)
uv run pytest tests/integration/ -v -m integration
Example Dataset for Pipeline Testing#
The repository includes infrastructure for testing the Snakemake pipeline without processing full production-scale recordings.
Programmatic synthetic data (CI-friendly)
A create_synthetic_dataset() helper in tests/integration/generate.py
builds a tiny directory tree with 8-channel NWB files that can be read back
by SpikeInterface. The example_dataset pytest fixture in
tests/conftest.py wraps this for convenient use:
def test_my_pipeline_step(example_dataset):
data_root = example_dataset["data_root"]
samples_config = example_dataset["samples_config"]
# ...
Integration tests in tests/integration/test_snakemake_flow.py demonstrate
discovery, filtering, and config-alias injection against this data.
Mini real dataset (committed bin/csv recordings)
Small real recordings are committed under .tests/integration/data/ and
exercised by pytest integration tests in TestMiniRealDataset. The dataset
uses {animal}/{index} placeholders with paired .bin / .csv files:
# Run mini-real integration tests
uv run pytest tests/integration/ -v -k TestMiniRealDataset
The full Snakemake run (via .github/workflows/snakemake-test.yml) also
exercises this dataset end-to-end. See config/datasets/mini_real.yaml
and config/samples_mini_real.json for the corresponding configuration.
Building Documentation#
Build docs locally:
make docs
# or: cd docs && uv run sphinx-build -b html . _build/html
Build with live reload (auto-refresh on changes):
make docs-live
Validation Scripts#
NeuRodent includes validation scripts to verify correctness and performance on real data.
These scripts are located in the scripts/ directory and should be run on the cluster.
Dask vs Serial Spike Detection Validation#
To verify that dask and serial multiprocess modes produce identical spike detection results:
# Request 10 cores for dask processing
srun -c 10 --pty bash
cd /mnt/isilon/marsh_single_unit/YY_PyEEG/neurodent_Joseph_devtree
# Run validation (default: 30 minutes of data)
uv run python scripts/validate_dask_serial_spike_consistency.py \
--recording-folder "/path/to/real/recording" \
--verbose
This script validates exact spike indices (sample-level precision) and measures performance.
Run this after modifying src/neurodent/core/frequency_domain_spike_detection.py.
When to run:
After modifying spike detection algorithm
Before releasing major versions
When investigating spike count discrepancies
Periodically (e.g., quarterly) as a sanity check
Expected output:
✅ All channels match perfectly at sample-level precision
Performance comparison (dask should be 1.5-2x faster)
Exit code 0 if identical, 1 if different
For full usage: python scripts/validate_dask_serial_spike_consistency.py --help
Writing Validation Scripts#
When creating validation scripts for the neurodent project:
Structure:
Location:
/scripts/directoryCLI interface via
argparsewith--helpoutputComprehensive module docstring (30-40 lines) with usage examples
Exit code 0 for success, 1 for failure
Use
uv run python scripts/<name>.pyin documentation
Docstring requirements:
Brief description of what the script validates
Important warnings (e.g., “Run on cluster only”)
Installation/setup requirements
Usage examples with real data paths (or placeholders)
Expected output description
Code style:
Follow project style (ruff/PEP8)
Helper functions with Google-style docstrings
Structured output with visual separators
Informative error messages
Documentation:
Add section to
/docs/contributing/testing.rstUpdate CLAUDE.md if establishing new patterns
Reference in README.md if user-facing
Example structure:
#!/usr/bin/env python3
"""
Brief description.
Detailed explanation.
IMPORTANT:
- Requirements/warnings
Usage:
uv run python scripts/script_name.py [options]
Example:
uv run python scripts/script_name.py --input /path/to/data
"""
import argparse
import sys
def helper_function():
"""Docstring."""
pass
def main():
parser = argparse.ArgumentParser(description="...")
# ...
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
Code Style#
We follow standard Python conventions:
Formatting: Use ruff for formatting and linting
Style: Follow PEP 8 guidelines
Types: Add type hints where appropriate
Docstrings: Use NumPy style