Testing#
Principles#
No mocks of the scientific code. Steps run the real MNE, PyPREP, mne-denoise and mne-icalabel code, so a dependency update that changes results is caught.
Ground truth where possible. Synthetic data are built so the right answer is known: line noise added to a line-free signal, a noisy and a flat channel at known positions, dipoles projected at known head positions. Tests compare against that truth, not against a previous output.
Contracts for every step. Every step passes
meeg_utils.testing.check_step(): parameters surviveclone,set_paramsand a YAML round trip;transformbeforefitraises; the input is not modified; channels and the time axis are unchanged unless declared; every declared figure can be drawn.
Layout#
tests/
├── conftest.py # small_raw, eeg, eeg_bad, neuromag fixtures
├── simulation.py # make_eeg, make_erp, make_chpi, make_opm
├── test_batch.py # meu.process
├── test_dataset.py # meu.Dataset and process_dataset
├── test_cli.py # the meu command
├── test_logging.py
├── test_core/ # Step and Pipeline (toy steps in _steps.py)
├── test_io/ # reading, system detection, derivatives
├── test_qc/ # acquisition checks, dataset summary, reports
├── test_epochs/ # Epoch, AutoReject, combining runs, epochs presets
└── test_steps/ # one file per step family (Maxwell, HFC, ...),
# contract + scientific checks; presets end to end
Simulations#
tests/simulation.py builds data whose right answer is known:
make_eeg(line=True, blinks=True, bad=False, seed=0)60 s of 32-channel EEG at 250 Hz: spatially smooth 1/f background, posterior alpha, frontal blinks with an EOG channel, 50 Hz line noise, and optionally a noisy (C4) and a flat (P8) channel.
make_erp(n_trials=60, erp=True)an oddball experiment with a P300 to the targets.
make_chpi(fname)the Neuromag test recording with cHPI coil signals and a known head movement added.
make_opm(axes=3)an OPM array (QuSpin coils) for HFC.
Fixtures#
eeg/eeg_badmake_eeg()without and with the bad channels.neuromag10 s of MNE’s Neuromag test recording (306 MEG + 60 EEG), downloaded once with a pinned URL and SHA-256 hash into the pooch cache; tests that need it are skipped when offline.
Real data#
Simulations cannot show everything (channel order, signal levels, missing
head shapes, memory use). docs/validation/ records runs on public
datasets, what they revealed and how to reproduce them; they are not part
of the test suite because of the download size.
Testing a custom step#
from meeg_utils.testing import check_step
def test_my_step_contract(eeg):
check_step(MyStep(param=1), eeg)
Running#
uv run pytest # everything (a few minutes)
uv run pytest tests/test_core tests/test_io # fast subset
uv run pytest tests/test_steps/test_head.py -k recovers
uv run pytest --cov=meeg_utils --cov-report=term-missing
Code quality checks run in CI as well:
uv run ruff check src tests
uv run ruff format --check src tests
uv run mypy src
uv run interrogate src