Testing¶
This guide covers the test suite for contributors to Agronomist. The project has two test layers: a Python test suite (primary) run with pytest, and a shell test suite (secondary) run with BATS that validates CI workflow scripts.
Python test suite (pytest)¶
The pytest suite is the primary test layer. It covers scanner logic, resolver behavior, report generation, configuration loading, and updater correctness.
Prerequisites¶
Install dependencies using Poetry:
Run all Python tests¶
This runs pytest with coverage enabled. The shorthand task is defined in pyproject.toml under [tool.taskipy.tasks].
Run with coverage report¶
This runs the full suite and enforces a coverage threshold. Use it before submitting a pull request.
Run a specific test file or test¶
# Single file
poetry run pytest test/unit/python/test_scanner.py
# Single test function
poetry run pytest test/unit/python/test_scanner.py::test_parse_git_source
Run with verbose output¶
Understand the output¶
A passing run reports a summary line like:
The exact count depends on the current test suite. Coverage output follows if you ran task test-coverage.
Writing new Python tests¶
Test files live under test/unit/python/. Use pytest conventions:
- File names must match
test_*.py. - Test functions must start with
test_. - Use
pytest.fixturefor shared setup. - Prefer small, focused tests over large integration-style test functions.
Example test:
from agronomist.scanner import _parse_git_source
def test_parse_git_source_with_module():
source = "git::https://github.com/org/repo.git//modules/vpc?ref=v1.2.3"
ref = _parse_git_source(source)
assert ref is not None
assert ref.ref == "v1.2.3"
assert ref.module == "modules/vpc"
Full check (lint + security + tests)¶
Before opening a pull request, run:
This runs, in order:
lint-- ruff check, ruff format, and mypy (linting, formatting, and static type checking)security-- bandit and eradicate (security scanning and dead code detection)test-- pytest with coverage (full test suite)
Shell test suite (BATS)¶
The BATS suite validates shell logic used in the multi-PR CI workflow scripts. It is secondary to the Python suite and requires additional system tooling.
Test structure¶
test/
├── unit/
│ ├── test_file_based_updates.bats # File-based update logic
│ └── test_multi_pr.bats # Multi-PR branch/PR creation logic
├── integration/
│ └── test_multi_pr_flow.sh # End-to-end workflow simulation
└── fixtures/
├── report_empty.json
├── report_single_module.json
└── report_multiple_modules.json
Install BATS dependencies¶
Or manually:
# Ubuntu / Debian
sudo apt-get install -y bats jq shellcheck git
# macOS
brew install bats-core jq shellcheck
Run BATS tests¶
# All BATS tests
bats test/unit/*.bats
# Single file
bats test/unit/test_multi_pr.bats
# Filter by name
bats test/unit/test_multi_pr.bats --filter "branch naming"
Run integration tests¶
This creates a temporary Git repository, applies fixture data, and validates branch creation behavior end-to-end.
Fixtures¶
Located in test/fixtures/. Used by both BATS and integration tests to represent different report shapes.
| File | Description |
|---|---|
report_empty.json |
Report with no updates ({"updates": []}) |
report_single_module.json |
One module with one file |
report_multiple_modules.json |
Three modules across multiple files |
Common issues¶
pytest not found:
Import errors in tests:
Always run pytest through Poetry:
BATS not found:
jq not found: