-
Notifications
You must be signed in to change notification settings - Fork 0
Setup testing #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Setup testing #37
Changes from all commits
9f329ff
46301df
ffd1aab
cbcc06b
237740b
13f98c6
8603c86
646859e
ec3db9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| # Testing Guide | ||
|
|
||
| Quick guide for running tests in this project. | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ```bash | ||
| # Install dependencies | ||
| pip install -e ".[dev]" | ||
|
|
||
| # Run fast tests (recommended during development) | ||
| pytest -m unit | ||
|
|
||
| # Run all tests (except slow ones) | ||
| pytest -m "not slow and not full_pipeline" | ||
|
|
||
| # Run everything | ||
| pytest | ||
| ``` | ||
|
|
||
| ## Test Categories | ||
|
|
||
| | Marker | Speed | | ||
| | --------------- | ------------ | | ||
| | `unit` | <1s per test | | ||
| | `integration` | 1-5 min | | ||
| | `slow` | >5 min | | ||
| | `full_pipeline` | 30+ min | | ||
|
|
||
| ## Common Commands | ||
|
|
||
| ### Development Workflow | ||
|
|
||
| ```bash | ||
| # Fast feedback loop (unit tests only) | ||
| pytest -m unit | ||
|
|
||
| # Test specific file | ||
| pytest tests/unit/test_bids_parsing.py | ||
|
|
||
| # Test specific function | ||
| pytest tests/unit/test_bids_parsing.py::test_parse_subject_id | ||
| ``` | ||
|
|
||
| <details> | ||
| <summary><h2>Useful Options</h2></summary> | ||
|
|
||
| ```bash | ||
| # Stop on first failure | ||
| pytest -x | ||
|
|
||
| # Show which tests are slowest | ||
| pytest --durations=10 | ||
|
|
||
| # Run tests matching a pattern | ||
| pytest -k "motion_correction" | ||
| ``` | ||
|
|
||
| </details> | ||
|
|
||
| ## Test Data | ||
|
|
||
| Test data is stored in `tests/data/`. | ||
|
|
||
| ## Coverage Requirements | ||
|
|
||
| - **Overall:** >85% | ||
| - **Unit tests:** >90% | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious if this will be possible - maybe with the dry runner it will
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it will be, at least for unit testing...integration testing might take longer.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant the 90% min
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We shall find out. Arbitrary numbers 🤷♂️ |
||
| - **Integration tests:** >80% | ||
|
|
||
| ```bash | ||
| # Check coverage | ||
| pytest --cov=src --cov-report=term-missing | ||
|
|
||
| # Generate HTML report | ||
| pytest --cov=src --cov-report=html | ||
| ``` | ||
|
|
||
| ## CI/CD | ||
|
|
||
| Tests run automatically on GitHub Actions: | ||
|
|
||
| - **On every push:** Unit tests + fast integration tests | ||
| - **Manual trigger:** Full pipeline tests (slow) | ||
|
|
||
| To run the same tests as CI locally: | ||
|
|
||
| ```bash | ||
| pytest -m "not slow and not full_pipeline" \ | ||
| --cov=src \ | ||
| --cov-report=xml \ | ||
| --junitxml=pytest.xml | ||
| ``` | ||
|
|
||
| ## Directory Structure | ||
|
|
||
| ``` | ||
| tests/ | ||
| ├── conftest.py # Shared fixtures | ||
| ├── unit/ # Fast tests (<1s) | ||
| ├── integration/ # Medium tests (1-5 min) | ||
| ├── full_pipeline/ # Slow tests (30+ min) | ||
| └── data/ # Test datasets | ||
| ``` | ||
|
|
||
| ## Best Practices | ||
|
|
||
| 1. **Run unit tests frequently** - They're fast (<1s) | ||
| 2. **Run integration tests before committing** - Catch issues early | ||
| 3. **Use appropriate markers** - Auto-applied based on file location | ||
| 4. **Write descriptive test names** - `test_motion_correction_preserves_dimensions` | ||
| 5. **One assertion per test** - When practical | ||
| 6. **Use fixtures** - Don't repeat setup code | ||
| 7. **Keep tests independent** - No shared state between tests | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| """Shared fixtures for tests data.""" | ||
|
|
||
| from pathlib import Path | ||
| from types import SimpleNamespace | ||
|
|
||
| import pytest | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def test_dataset_dir() -> Path: | ||
| """Return path to test dataset directory.""" | ||
| return Path(__file__).parent / "data" / "ds000001" | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def test_subject(test_dataset_dir: Path) -> SimpleNamespace: | ||
| """Return namespace containing file paths to test subject data.""" | ||
| subject_id = "01" | ||
| task_id = "balloonanalogrisktask" | ||
|
|
||
| subject_dir = test_dataset_dir / f"sub-{subject_id}" | ||
| anat_dir = subject_dir / "anat" | ||
| func_dir = subject_dir / "func" | ||
|
|
||
| subject_data = SimpleNamespace( | ||
| subject_id=subject_id, | ||
| subject_dir=subject_dir, | ||
| t1w=anat_dir / f"sub-{subject_id}_T1w.nii.gz", | ||
| bold=func_dir / f"sub-{subject_id}_task-{task_id}_run-01_bold.nii.gz", | ||
| tasks=test_dataset_dir / f"task-{task_id}_bold.json", | ||
| events=func_dir / f"sub-{subject_id}_task-{task_id}_run-01_events.tsv", | ||
| ) | ||
|
|
||
| required_files = { | ||
| "T1w": subject_data.t1w, | ||
| "BOLD": subject_data.bold, | ||
| "task": subject_data.tasks, | ||
| "events": subject_data.events, | ||
| } | ||
| for name, fpath in required_files.items(): | ||
| if not fpath.exists(): | ||
| raise FileNotFoundError(f"{name} file not found: {fpath}") | ||
| return subject_data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe keep abridged version in README and move full guide to seperate markdown doc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or full guide as section in contributing md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, make each less relevant subsection a dropdown. Not the biggest fan of that, but 🤷. I'll take a look to see what I did for other repos - probably not a lot 😂