Skip to content

Pytest Naming Conventions and Test Organization

Agent Quick Path

Use this when creating or reorganizing test modules so naming and hierarchy stay predictable.

  1. Mirror the product domain structure in tests/ so ownership is obvious.
  2. Encode broad context in module and class names (test_*.py, Test*).
  3. Keep leaf test names short and behavior-focused (test_*).
  4. Use class Test<Subject>: only for grouping related scenarios.
  5. Place fixtures in the nearest conftest.py needed by scope.
  6. Separate expensive tests with markers first, directories second.

Naming Conventions

File and directory naming

  • Use lowercase snake_case for test file names: test_user_service.py.
  • Keep directories domain-oriented and stable over time: tests/orders/, tests/billing/.
  • Prefer descriptive test names over internal ticket numbers or implementation details.

Test function naming

  • Prefer hierarchical naming: put broad context in folder/module/class, and keep the function name focused on the final assertion.
  • Keep pytest discovery prefixes intact:
  • modules start with test_
  • classes start with Test
  • functions start with test_
  • Start with user-visible behavior or contract, not private helper names.

Recommended pattern: - module: test_<subject>.py - class: Test<Operation> or Test<Scenario> - function: test_<expected_outcome>

Examples: - Flat (still valid): test_create_order_rejects_invalid_currency - Class-context: TestOrder -> TestCreate -> test_rejects_invalid_currency - Module-context: test_order.py -> TestCreate -> test_rejects_invalid_currency - Module + class context can similarly shorten: - test_token.py -> TestRefresh -> test_rotates_session_id - test_user_list.py -> TestListUsers -> test_returns_empty_for_new_tenant

Test class naming

  • Use class Test<SubjectOrScenario>: for scenario grouping and context reduction.
  • Keep class names noun-focused (TestOrderService) rather than action-focused.
  • Avoid xUnit style setup inheritance when fixtures can express dependencies directly.

Hierarchy and Organization Patterns

Two patterns work well; choose one and apply it consistently.

Pattern A: Source-mirror hierarchy (default for product code ownership)

src/
  app/
    orders/service.py
    billing/invoice.py

tests/
  app/
    orders/test_service.py
    billing/test_invoice.py

Use this when teams own modules by source path and want direct test-to-source mapping.

Pattern B: Cost-lane hierarchy (default for CI policy clarity)

tests/
  unit/
    orders/test_service.py
  integration/
    api/test_orders.py
    persistence/test_order_repository.py
  smoke/
    test_health.py

Use this when CI gating is based on cost lanes and marker filtering.

  • Keep a source-mirror tree for local ownership.
  • Add markers (unit, integration, smoke, external) for runtime policy.
  • Avoid duplicating both trees unless the repository already requires it.

Fixture Placement Strategy

  • Put universal lightweight fixtures in tests/conftest.py.
  • Put domain fixtures in subtree conftest.py files close to where they are used.
  • Keep fixtures composable and explicit; avoid large fixture "god objects".
  • Use yield fixtures for teardown so cleanup is always paired with setup.

Parametrize and ID Naming

  • Use pytest.mark.parametrize for behavior matrices instead of copy/paste tests.
  • Provide explicit ids= labels when case names are not obvious.
  • Keep IDs business-meaningful ("expired-token", "zero-balance") so failures are readable.

Collection and Structure Checks

Use these checks after introducing new test files or renaming modules:

  • uv run pytest --collect-only -q
  • uv run pytest -m unit -q
  • uv run pytest -m "not external" -q

If collection surprises appear, verify file names, marker registration, and directory placement first.

Common Anti-Patterns

  • Mixed naming styles (testFoo.py, test_foo.py, foo_test.py) in one repository.
  • Deep fixture chains that hide setup behavior.
  • Test names that encode implementation details instead of behavior.
  • Moving slow tests into unit directories without marker updates.
  • Sharing mutable module-level state across tests.