pytest naming convention
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
# 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<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)
|
||||
|
||||
```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.
|
||||
Reference in New Issue
Block a user