Rules for Writing Tests¶
The "Two Hats" Rule¶
- Hat 1 - Test Rules for Features & Fixes:
- Write unit tests targeting only the new functionality or regression bug.
-
Do not modify existing, unrelated test files or refactor test fixtures.
-
Hat 2 - Test Rules for Structural Refactoring:
- Mirror Code Structure: When a module is split or reorganised (e.g., decomposing a monolithic file into a sub-package), refactor the test suite concurrently to mirror the new code layout (e.g., splitting a single test file into corresponding test sub-modules).
- Import & Setup Rewiring: Update import statements, path references, and test setup/fixtures to match new class or sub-module locations.
- Patch Targets: When a function moves, update
mock.patchandmonkeypatch.setattrtarget strings to the new module path. Path-only edits are permitted; assertions stay frozen. - Strict Assertion Freeze: Never alter test logic, expected outputs, or
assertstatements during structural test refactoring. - No Coverage Shrinkage: Do not delete existing test cases during Hat 2 execution — the test suite must strictly prove behavioural equivalence before and after the code moves.
Standards¶
- Framework:
pytestONLY. Do not use theunittestlibrary, includingunittest.mock. Use thepytest-mockmockerfixture orpytest.MonkeyPatchfor external boundaries. Existingunittest.mockimports are quarantined under the legacy rule and are converted topytest-mockwhen their test module is touched. - Style: Standalone
test_*functions with single-line comments above each for VS Code outlines. - Line Limit: 79-character limit applied to test code and fixture data structures.
- File Length: Keep new and re-structured test modules under ~800 total
lines. Split suites by responsibility rather than accumulating a monolithic
test_all.py. Legacy oversize suites are quarantined until touched.
Test Function Granularity¶
- Function Size: Individual
@pytesttest functions SHOULD be short (under 15 statements). - Single Focus: Each test function should test only one assertion or scenario/edge-case.
- Parametrization: Use
@pytest.mark.parametrizeto test multiple input/output variations rather than duplicating test functions.
Directory Layout¶
Organize under tests/:
- unit/: Pure logic, no I/O, uses mocks.
- functional/: File system and local resource I/O.
- integration/: Non-Python dependencies or external services.
- data/: Tracked input assets (git) and temporary output cleanup.
- fixtures/: Common setup/teardown capabilities.