wyattowalsh docs
DevelopmentTesting

Testing

pytest with hypothesis, parallel execution, coverage, and golden-file regression tests.

Stack

ToolPurpose
pytestTest runner
pytest-xdistParallel test execution (-n auto)
hypothesisProperty-based / fuzz testing
pytest-covCoverage reporting
pytest-mockMock utilities

Run tests

# Full suite (parallel) via CLI
uv run readme dev test

# Direct pytest (equivalent)
uv run python -m pytest

# Single file, serial (useful for debugging)
uv run python -m pytest tests/test_cli.py -v -n 0

# Only non-integration tests
uv run python -m pytest -m "not integration" -v

# Filter by expression
uv run readme dev test -k "test_banner"

# Skip coverage
uv run readme dev test --no-coverage

pytest configuration

From pyproject.toml:

[tool.pytest.ini_options]
addopts = "-n auto --verbose --hypothesis-show-statistics --html=logs/report.html ..."
testpaths = ["tests"]
timeout = 500

Coverage is written to logs/coverage/ (HTML) — excluded from git.

Test files

FileWhat it tests
tests/test_cli.pyAll CLI commands via typer.testing.CliRunner (imports from scripts.cli package)
tests/test_ink_garden.pyink_garden.generate() smoke + golden-file regression
tests/test_word_clouds_red.pyRED (xfail) stubs for unimplemented word-cloud features
tests/test_living_art_media.pySkipped — requires pre-generated artifacts
tests/test_readme_gfm_ux.pySkipped — requires full pipeline README
tests/test_techs.pyTechnology parser: parse, validation, Hypothesis fuzz
tests/test_banner.pyBanner generation (patches svgwrite)
tests/test_fetch_metrics.pyMetrics fetcher (expanded coverage)
tests/test_readme_sections.pyREADME section assembler
tests/test_readme_svg.pySVG rendering helpers
tests/test_skills.pySkills badge generator
tests/test_qr.pyQR code generation (Cairo integration)
tests/test_utils.pyLogger and utility functions
tests/test_card_contracts_blog_red.pyRED — unimplemented blog card features

Golden-file tests

tests/test_ink_garden.py::TestGoldenFiles compares deterministic SVG output against stored fixtures:

tests/fixtures/ink_garden/
├── minimal_full.svg   # ~160 KB
├── rich_full.svg      # ~1.4 MB
└── rich_mid.svg       # ~1.4 MB

To regenerate after an intentional output change:

# Delete fixtures and re-run to regenerate
rm -rf tests/fixtures/ink_garden/
uv run python -c "
from scripts.art.ink_garden import generate, seed_hash
import pathlib
# ... (see test file for full snippet)
"
uv run python -m pytest tests/test_ink_garden.py -v

Mocking conventions

  • Heavy rendering (svgwrite, cairo) — patched via @patch at the source module
  • Lazy imports — patch at scripts.banner.generate_banner, NOT scripts.cli.generate_banner
  • Config I/O — use tmp_path fixture + real YAML files (no config mocking)
  • Do not mock the database (N/A) or the filesystem with MagicMock (use tmp_path)

Loguru handler reset

scripts/utils.py calls loguru_logger.remove() at import time. Tests that import any scripts.* module need the reset_loguru_handlers autouse fixture (see test_utils.py).

RED tests (xfail)

tests/test_word_clouds_red.py contains three @pytest.mark.xfail(strict=True) stubs for features not yet implemented:

  • palette_tokenization field
  • layout_readability knobs
  • style_variant output differentiation

These tests will start passing automatically when the features land.