Skip to content

Test Design

/test-cases designs cases into syntaxis/qa/cases/<area>.md. It runs nothing and proves nothing: a case is a specification of an observation someone will make later, by hand or through automation.

The source of truth, in order:

  1. syntaxis/context/current-feature.md — the active spec’s done-when criteria, the primary source
  2. syntaxis/build-plan.md — the item and its stable number
  3. syntaxis/qa/test-plan.md — scope, risk priority, target platforms
  4. syntaxis/context/project-overview.md — product rules

The implementation is read last, and only to find reachable states a requirement implies but does not spell out. When the code contradicts the spec, that is a defect to report, not a requirement to encode into a case.

For each requirement, cases cover:

Dimension What it means
Expected path The requirement met, in the normal way
Boundaries Empty, minimum, maximum, one past maximum, zero, negative, largest realistic
Invalid input Wrong type, wrong format, malformed, and hostile input where untrusted users reach the field
State and permission Logged out, wrong user, insufficient role, expired session, another user’s record
Interruption Network loss, failed dependency, duplicate submit, back-navigation mid-flow

Mobile surfaces add permission grant and denial, background and resume, offline and reconnect, rotation, display sizes, and interruption by a call or notification — but only for platforms the test plan actually targets.

Coverage is derived from what the requirement claims. Cases are never padded to hit a count, and a requirement with no observable outcome is reported as untestable rather than given a case that asserts nothing.

### TC-014 [P1] manual - Checkout rejects an expired card
**Covers:** feature 7 / done-when 3
**Preconditions:** A signed-in account with one saved expired card.
**Steps:**
1. Open the cart with one item and choose Checkout.
2. Select the saved card ending 4242.
3. Submit the order.
**Expected:** The order is refused, the message names the expiry as the
reason, the cart keeps its item, and no charge is created.
**Platforms:** web, iOS
**Notes:**

The header line is a machine-readable contract:

### TC-<id> [<priority>] <automation status> - <title>
Field Rules
TC-NN Sequential across the whole cases/ tree. Never reused, never renumbered.
Priority P0P3, by the consequence of the behaviour failing
Automation status manual, automated, or retired. /automate owns the move to automated.

Required, and must name a real build-plan item, done-when criterion, or documented product rule. This is the traceability link: it is what lets /test-cases report which requirements have no coverage.

One observable outcome per case, specific enough that two people would agree on pass or fail. “Works correctly” is not an expected result.

State the negative half when there is one. “The cart keeps its item and no charge is created” is two assertions, and a test that only checks the error message does not cover this case.

What a tester does, in the user’s terms. Never internal functions, database tables, or component names.

TC-NN values are sequential across the whole tree, one past the highest ID present anywhere. They are never reused and never renumbered, even after a case is retired.

This matters because old cycle records in syntaxis/qa/runs/ reference them. A renumbered case silently rewrites history that was already recorded as evidence.

Retire rather than delete. Set the status to retired and say why in Notes.

/test-cases finishes by stating honestly:

  • requirements with no case
  • cases whose Covers no longer matches a live requirement
  • areas the test plan calls high risk that remain thin
  • untestable requirements, and why
  • contradictions found between spec and implementation, which go to /fix or /debug
/test-cases regression

This selects from existing cases across areas. It does not invent new ones.