5.3 KiB
Testing
This page defines an initial, high-level test structure for this repository that is easy to grow over time while keeping local feedback fast.
Primary guidance sources:
Goals
- Keep the default developer test loop fast and deterministic.
- Separate cheap tests from expensive tests with clear markers.
- Mirror production code layout so new tests are easy to place.
- Make FastAPI dependency and lifespan testing explicit.
- Leave clear extension points for database and external integrations.
Naming And Layout Conventions
Use these naming rules consistently:
- test files:
test_<subject>.py - test functions:
test_<behavior>_<expected_result>() - test classes (optional):
Test<Subject> - fixtures:
<scope>_<resource>(for examplesession_engine,api_client)
Target end-state test tree:
tests/
conftest.py
unit/
test_main.py
test_mcp.py
catalog/
test_server.py
skills/
test_document_loader.py
test_document_loader_references.py
web/
test_config.py
test_docs_mount.py
integration/
api/
test_health_endpoint.py
test_app_routes.py
catalog/
test_prompt_loader_and_catalog.py
test_step6_tool_fallback.py
startup/
test_lifespan.py
smoke/
test_service_boot_and_health.py
fixtures/
__init__.py
factories.py
payloads.py
resources/
prompts/
skills/
catalog/
configs/
Mapping rule:
- mirror
src/personal_mcp/intotests/unit/first. - add integration modules only where contracts exist (FastAPI routes, DB behavior, external adapters).
Current to target mapping in this repo:
tests/test_document_loader_references.py->tests/unit/skills/test_document_loader_references.pytests/test_prompt_loader_and_catalog.py->tests/integration/catalog/test_prompt_loader_and_catalog.pytests/test_step6_tool_fallback.py->tests/integration/catalog/test_step6_tool_fallback.py
Marker Strategy
Use a small marker vocabulary from day one:
unit: pure logic, no DB/network/filesystem side effects.integration: framework wiring and/or DB contract checks.smoke: thin checks for critical paths.slow: expensive tests that should not run in every local loop.external: real third-party calls; typically excluded in CI by default.
Fast-path expectation:
- local default loop should prioritize
unit(and optionallysmoke).
Fixture Layering
- Keep lightweight, global fixtures in
tests/conftest.py. - Add subtree
conftest.pyfiles only when a test group needs dedicated setup. - Prefer fixture composition over large monolithic fixtures.
- Always clean up FastAPI dependency overrides in fixture teardown.
Suggested early fixtures:
settings_overrideapi_clienttmp_workspace
Optional integration fixtures (add when needed):
session_engine/db_sessionasync_engine/async_session
FastAPI Testing Structure
Default route tests:
- use
fastapi.testclient.TestClientfor standard route behavior tests. - keep tests as sync
defunless async behavior must be asserted.
Async route tests:
- use
httpx.AsyncClientwithASGITransportand@pytest.mark.anyio.
Dependency testing:
- prefer
app.dependency_overridesforDepends(...)seams. - reset
app.dependency_overridesafter each test/fixture.
Lifespan behavior:
- use
TestClient(app)as a context manager for startup/shutdown checks.
Test Granularity By Layer
unit/:
- parser and loader behavior
- catalog indexing and filtering logic
- utility functions and pure transformations
integration/api/:
- endpoint request/response contracts
- dependency override behavior
- error mapping and status code assertions
integration/db/ (future-ready):
- transaction boundaries
- commit/rollback semantics
- async session lifecycle behavior
smoke/:
- one request/assertion path per critical workflow
Command Baseline
Canonical invocation in this repository:
uv run pytest
Recommended execution sequence:
uv run pytest --collect-only -q
uv run pytest -m unit -q
uv run pytest -m "unit or smoke" -q
uv run pytest -q
Extension Plan
When adding a new feature:
- Add or update corresponding
unittests first. - Add an
integrationtest only for real boundary/contracts. - Add/adjust fixtures at the narrowest useful scope.
- Add a
smoketest only for user-critical paths.
When the suite grows:
- Split slower groups behind
slowand/orexternalmarkers. - Keep
unitruntime bounded for rapid local feedback. - Promote shared setup into fixtures only after repeated duplication.
Rollout Order
Use this order to reach the end-state structure with minimal disruption:
- Create
tests/unit/andtests/integration/subtrees. - Move the 3 existing top-level tests into the mapped target paths.
- Add
tests/unitmodules mirroring uncovered source modules. - Add API and lifespan integration tests under
tests/integration/api/andtests/integration/startup/. - Add one smoke test for boot + health path.
- Add marker registration in
pyproject.tomlas marker usage expands.
This gives a concrete, stable final organization that remains easy to extend as new modules and boundaries are added.