Aller au contenu

Pytest patterns

Status: Placeholder — to be developed. Last reviewed:Reference structural sibling: guidelines/i18n/translation-rules.md (process + rules style).

Scope (when this guideline lands)

Conventions for tests: directory layout, fixture scope, factory usage, integration vs. unit boundary, when to hit the real DB, naming, what make test / make test-k / make test-f / make test-x actually do.

Out of scope (cross-refs)

  • Test data reset for development (docker-reset-test-data.sh) → docs/RESET_TEST_DATA.md (reference). Test fixture data and dev test data are different concerns.
  • CI configuration (where tests run, environment setup) → guidelines/development-workflow/overview.md (Draft, currently has the deploy info).
  • Migration testing (does make migrate work cleanly on a fresh DB?) → guidelines/backend/migrations.md (placeholder).

Sources to mine when writing this

  • conftest.py (root) and any per-app conftest.py files. Document every fixture.
  • pytest.ini configuration. Document every option.
  • Makefile test targets — test, test-seq, test-k, test-f, test-x, test-coverage. Document what each one is for.
  • Existing test factories (factory_boy or hand-rolled) — converge on one pattern.
  • The recent test_entity_holder fix (commit 76c7d45 — "pin entity name, not just code") — that's a worth-documenting lesson about test brittleness.
  • Per-app tests/ directories — survey for outliers.

Starter hard rules to investigate

  1. Pytest, not unittest: every test file uses pytest fixtures, not TestCase setUp/tearDown.
  2. Factories over hand-rolled fixtures: every model has a factory in apps/<app>/tests/factories.py.
  3. Real DB integration over mocks for migration safety (per MEMORY.md user feedback: "Don't mock the database — we got burned when mocked tests passed but prod migration failed").
  4. Test names describe behaviour, not implementation: test_user_with_no_practice_access_cannot_view_patient_list, not test_user_view.
  5. pytest.mark.django_db on every test that hits the DB, no exceptions — the speed cost is real but the implicit-DB confusion cost is higher.

Decision points to settle

  1. Fixture scope defaults: function vs module vs session. Codebase has all three; pick a default with clear "use X when Y" rules.
  2. pytest.mark.slow: a marker for expensive tests (e.g. full Doctolib API replay). When to mark, how to skip in fast cycles (make test-fast?).
  3. Mocking external services (Doctolib API, Pennylane, Sentry): when is mocking acceptable despite the "real-DB" rule? (Probably: external HTTP yes, internal DB no.)
  4. Test data realism: factory defaults that look like real data vs deliberately weird (Faker? curated minimal data? both?).
  5. Coverage targets: make test-coverage exists — is there a minimum threshold? CI gate?

Known deviations to look for during writing

  • Tests using unittest.TestCase instead of pytest fixtures.
  • Tests that mock Django's ORM (the bug MEMORY.md warns against).
  • Tests without pytest.mark.django_db that silently fail-open.
  • Hand-rolled factories that should use factory_boy.
  • Tests pinned to model field values that change with i18n source rewrites (a future i18n change risks breaking them — flag for refactor to use stable identifiers).

If found, file as roadmap/backlog/testing-pytest-drift-2026-MM.md.