📝 Require labeled AAA blocks in tests
Rule in CONTRIBUTING.md; decision, why and rejected unlabeled ordering in development/testing.md.
This commit is contained in:
1 parent
34567856a5
commit
62a5599ab3
2 files changed
+33
No files matched your search
@@ -67,6 +67,12 @@ before the implementation. The loop is **type → red → green → refactor**:
|
|||||||
`npm run verify` as the definition-of-done gate.
|
`npm run verify` as the definition-of-done gate.
|
||||||
|
|
||||||
Every test pairs an `expectTypeOf(...)` with an `assert.*`; keep them together.
|
Every test pairs an `expectTypeOf(...)` with an `assert.*`; keep them together.
|
||||||
|
Each test body follows **AAA (Arrange–Act–Assert)** with labeled blocks
|
||||||
|
separated by a blank line: `// Arrange` sets up the inputs (e.g. the matcher
|
||||||
|
factory), `// Act` exercises the subject once from them (not a second
|
||||||
|
throwaway call), `// Assert` holds every check — type expectations first,
|
||||||
|
runtime assertions last; an empty block drops its label (see
|
||||||
|
[development/testing.md § AAA ordering](./development/testing.md#aaa-ordering)).
|
||||||
Type-first is enforced structurally: `npm test` runs `check:tsc` before the
|
Type-first is enforced structurally: `npm test` runs `check:tsc` before the
|
||||||
test runner, so a wrong type can never be papered over by a passing assertion.
|
test runner, so a wrong type can never be papered over by a passing assertion.
|
||||||
Per [AGENTS.md § Never do](./AGENTS.md#never-do), reach green honestly — fix the
|
Per [AGENTS.md § Never do](./AGENTS.md#never-do), reach green honestly — fix the
|
||||||
|
|||||||
@@ -36,6 +36,33 @@ The runner is `node --test --strip-types "src/**/*.test.ts"` and the tiers are i
|
|||||||
build step, and the runner relies on the `.ts` import-extension convention (see
|
build step, and the runner relies on the `.ts` import-extension convention (see
|
||||||
[tooling.md](./tooling.md#source-imports-use-ts-extensions)).
|
[tooling.md](./tooling.md#source-imports-use-ts-extensions)).
|
||||||
|
|
||||||
|
## AAA ordering
|
||||||
|
|
||||||
|
The rule is in
|
||||||
|
[CONTRIBUTING.md § Testing discipline (type-driven)](../CONTRIBUTING.md#testing-discipline-type-driven).
|
||||||
|
|
||||||
|
#### Decision (2026-09)
|
||||||
|
|
||||||
|
Test bodies read arrange → act → assert: inputs (the factory) set up first, the
|
||||||
|
subject exercised once from them, all checks last — types then runtime. The
|
||||||
|
blocks are labeled with `// Arrange` / `// Act` / `// Assert` comments and
|
||||||
|
separated by a blank line; an empty block drops its label.
|
||||||
|
|
||||||
|
#### Why
|
||||||
|
|
||||||
|
- Interleaved setup/checks hide what runs vs. what is observed; the eye
|
||||||
|
re-reads the block to find the seams.
|
||||||
|
- A factory built mid-test invites a second throwaway call of the subject;
|
||||||
|
arranging it once makes the positive construction and the negative
|
||||||
|
`Parameters<…>` check share one source of truth.
|
||||||
|
- Labels make the seams explicit, not inferred — grep-able and reviewable
|
||||||
|
without reading the statements.
|
||||||
|
|
||||||
|
#### Rejected
|
||||||
|
|
||||||
|
- Unlabeled ordering (bare blank lines): the seams still have to be found by
|
||||||
|
reading; the labels cost nothing.
|
||||||
|
|
||||||
## Known issues
|
## Known issues
|
||||||
|
|
||||||
- The type-aware linter misidentifies `expectTypeOf()` as a floating promise, so
|
- The type-aware linter misidentifies `expectTypeOf()` as a floating promise, so
|
||||||
|
|||||||
Reference in new issue
Block a user