# Pytest Naming Conventions and Test Organization !!! info "Primary sources" - [Good integration practices](https://docs.pytest.org/en/stable/explanation/goodpractices.html) - [Changing standard (Python) test discovery](https://docs.pytest.org/en/stable/example/pythoncollection.html) - [How to use fixtures](https://docs.pytest.org/en/stable/how-to/fixtures.html) - [How to parametrize fixtures and test functions](https://docs.pytest.org/en/stable/how-to/parametrize.html) - [Marker examples](https://docs.pytest.org/en/stable/example/markers.html) ## 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:` 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_.py` - class: `Test` or `Test` - function: `test_` 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:` 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) ```text 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) ```text 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. ### Hybrid rule (recommended) - 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.