You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
### Principle 5: Python Unit Test Conventions [PID:K-5]
310
+
Python unit tests (pytest) MUST follow standardized layout, naming, isolation, determinism, and readability rules that complement PID:A-1 through PID:A-4.
311
+
Scope:
312
+
- Applies to all files under `tests/` for Python; integration tests are pytest-based but MAY be excluded from default fast run via markers.
313
+
314
+
Layout & Naming:
315
+
- Test files MUST be named `test_*.py`; NEVER mix production & tests in one file.
316
+
- File/dir mirroring MUST follow PID:A-2 (e.g., `release_notes_generator/util/paths.py` → `tests/unit/release_notes_generator/util/test_paths.py`).
317
+
- Integration tests (optional) MUST reside under `tests/integration/` or carry `@pytest.mark.integration` added by collection hooks; they MUST NOT run by default when invoking bare `pytest`.
318
+
- Fixture names MUST use `snake_case` expressing purpose (e.g., `tmp_repo`, `make_user`).
319
+
320
+
Functions vs Classes:
321
+
- Default style: free test functions. Classes MAY be used ONLY when one of: (a) a shared class-level marker applies to all tests, (b) a repeated complex fixture pattern benefits from grouping, (c) they group clearly related modes (happy path / edge / error) improving readability.
322
+
- Test classes MUST be named `Test<ThingOrBehavior>`; MUST NOT define `__init__`, inheritance, or state; methods STILL start with `test_`.
323
+
- Shared setup MUST prefer fixtures over `setup_class` / `teardown_class` unless a fixture cannot express the pattern.
324
+
325
+
Execution Style & Readability:
326
+
- Tests MUST express Arrange → Act → Assert (AAA) blocks separated by blank lines or comments; multiple asserts allowed when validating facets of a single behavior.
327
+
- Prefer `@pytest.mark.parametrize` over manual loops or duplication to express input→output tables.
328
+
329
+
Fixtures & Conftest Hygiene:
330
+
- Fixtures MUST be small & composable; file-local unless broadly reused (then move to `tests/conftest.py`).
331
+
-`tests/conftest.py` MUST contain ONLY shared fixtures, hooks, and marker definitions—no production logic or test bodies.
332
+
- Fixture default scope MUST remain `function`; broader scopes (`module`, `session`) MAY be used ONLY when measurably faster AND no state leakage occurs.
333
+
334
+
Isolation & Determinism:
335
+
- Unit tests MUST NOT perform real network calls, sleeps, or rely on wall-clock time; use fakes, `monkeypatch`, `tmp_path`, and passed clocks.
336
+
- Randomness MUST be seeded or controlled inside the test; time-dependent code MUST patch or inject clock sources.
337
+
- Global or mutable module state MUST NOT leak between tests; if unavoidable, MUST be reset via fixture finalizers.
338
+
339
+
Assertions & Failure Clarity:
340
+
- Prefer direct equality assertions (`assert value == expected`) for auto-diffs over boolean conditions.
341
+
- Exception checks MUST use `pytest.raises(<ExactType>, match=<substring>)` when message validation adds value.
342
+
- Logging checks MUST set explicit level (`with caplog.at_level("WARNING")`) before asserting log text.
343
+
344
+
Markers & Classification:
345
+
-`slow` and `integration` tests MUST be explicitly marked and excluded from the default fast run via `pytest.ini` configuration.
346
+
-`smoke` marker MAY identify a minimal high-signal subset.
347
+
- Known unfixed bugs MUST use `pytest.mark.xfail(strict=True, reason="bug #<id>")`; accidental passes then fail CI.
348
+
-`skip` MUST only guard true environmental/precondition absence (e.g., OS-specific behavior).
349
+
- Internal-only tests touching non-public functions MUST be named `test__internal_*` OR marked `@pytest.mark.internal`.
350
+
351
+
Test Doubles:
352
+
- Fakes (simple dataclasses, lambdas, small in-memory implementations) SHOULD be preferred over deep mocks.
353
+
- Monkeypatches MUST patch where an object is looked up (import site) not where originally defined.
354
+
355
+
Performance:
356
+
- Individual unit tests SHOULD target <100ms local runtime; slower tests MUST be marked appropriately or optimized.
357
+
- Default `pytest` invocation MUST yield a fast, offline suite.
358
+
359
+
State & Output Tools:
360
+
- Use `tmp_path` for filesystem isolation, `monkeypatch` for environment/attribute overrides, `capsys` for stdout/stderr, and `caplog` for logging.
361
+
- Tests MUST NOT rely on implicit logging levels; they MUST set levels explicitly when asserting logs.
362
+
363
+
Decision Checklist (Guidance / NON-NORMATIVE):
364
+
- Is a class genuinely clearer than multiple free functions? If not, keep functions.
365
+
- Can variations be expressed with parametrization? If yes, parametrize.
366
+
- Does the test touch network/time/FS? If yes, isolate or mark.
367
+
- Does failure message clearly show diff? If not, prefer equality or add `match=`.
368
+
369
+
Rationale: Ensures consistent, deterministic, maintainable Python tests; reduces duplication, improves readability & diagnostics, and preserves fast feedback loops without sacrificing clarity.
When adding a new import, it MUST be placed into the existing top-of-file grouped import block (see PID:K-4) without creating duplicate group separators or resorting the entire file unnecessarily.
0 commit comments