Skip to content

Commit dcf920a

Browse files
#203 - Refactor custom chapter using new principles (#204)
* #203 - Refactor custom chapter using new principles - Custom chapter unit test file refactored.
1 parent d0e2307 commit dcf920a

File tree

3 files changed

+313
-279
lines changed

3 files changed

+313
-279
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,5 +158,9 @@ cython_debug/
158158
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
159159
.idea/
160160

161+
# Added common OS/editor artifacts
162+
.DS_Store
163+
.vscode/
164+
161165
default_output.txt
162166
run_locally.sh

.specify/memory/principles.md

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -262,22 +262,22 @@ Scope Separation:
262262

263263
Preferred (Canonical) Function / Method Template (RECOMMENDED):
264264
"""
265-
<One-sentence imperative summary.>
265+
One-sentence imperative summary.
266266

267267
Parameters:
268-
- <name>: <concise description>
268+
- name: concise description
269269
- ...
270270

271271
Returns:
272-
- <Description of value semantics.>
272+
- Description of value semantics.
273273

274274
Raises: (optional)
275-
- <ExceptionType>: <condition>
275+
- ExceptionType: condition
276276
"""
277277

278278
Canonical Class Template:
279279
"""
280-
<Class purpose in one sentence (declarative is acceptable).>
280+
Class purpose in one sentence (declarative is acceptable).
281281
"""
282282

283283
Rules:
@@ -306,6 +306,68 @@ Rules:
306306
- Wildcard imports (`from x import *`) forbidden.
307307
Rationale: Improves clarity, reduces merge conflicts, enables deterministic isort enforcement, and surfaces dependency scope early.
308308

309+
### 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.
370+
309371
### Principle 6: Import Insertion Discipline [PID:K-6]
310372
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.
311373
Rules:

0 commit comments

Comments
 (0)