Skip to content

Test Plan as Spec

Write the test plan first as the authoritative feature specification. Each acceptance criterion contains the exact test code that must pass, so the test plan and the spec are literally the same document — there is nothing to drift.

Problem

When specs and tests live in separate documents, they drift apart:

  • A spec says "filter selects matching values" but the test asserts different edge cases
  • A criterion says "supports merging streams" but no test covers it
  • Tests pass but the spec has unimplemented criteria nobody notices
  • New team members read the spec, then discover the tests contradict it

The root cause is duplication: two documents describing the same behavior will eventually disagree.

Solution

Embed the test code directly inside each acceptance criterion. The acceptance criteria checklist is the test plan. One document, one source of truth.

Pattern

1. Organize criteria into phases

Group related criteria under phase headers. Each phase builds on the previous one — later phases assume earlier ones pass.

markdown
## Target

**Goal:** Implement CartService with full test coverage

### Acceptance Criteria

#### Phase 1: CartService API

- [ ] Task 1.1: exports `CartService` class
- [ ] Task 1.2: has `addItem` method
- [ ] Task 1.3: has `removeItem` method
- [ ] Task 1.4: has `getTotal` method

#### Phase 2: CartService Behavior

- [ ] Task 2.1: `addItem` adds item to cart
- [ ] Task 2.2: `removeItem` removes item from cart
- [ ] Task 2.3: `getTotal` returns sum of item prices

2. Include test code in each criterion

Every criterion includes the exact test that verifies it. The test IS the specification — there is no separate description to contradict.

markdown
- [ ] Task 1.1: exports `CartService` class

```js
import { describe, test } from "node:test";
import assert from "node:assert/strict";
import { CartService } from "./index.js";

describe("CartService", () => {
  describe("module exports", () => {
    test("exports CartService class", () => {
      const actual = typeof CartService;
      const expected = "function";
      assert.strictEqual(actual, expected);
    });
  });
});
```

- [ ] Task 1.2: has `addItem` method

```js
test("has addItem method", () => {
  const cart = new CartService();

  const actual = typeof cart.addItem;
  const expected = "function";
  assert.strictEqual(actual, expected);
});
```

3. Start with API surface, then behavior

Phase 1 criteria verify the public interface exists — exports, methods, static methods, return types. Phase 2+ criteria verify behavior — what happens when you call those methods.

This ordering means API tests act as a contract: consumers know exactly what the module exposes before any behavior is wired up.

markdown
#### Phase 1: API Surface

- [ ] Task 1.1: exports `CartService` class
- [ ] Task 1.2: has `addItem` method
- [ ] Task 1.3: has `removeItem` method

#### Phase 2: Core Behavior

- [ ] Task 2.1: `addItem` adds item with name and price
- [ ] Task 2.2: `addItem` increments quantity for duplicate items
- [ ] Task 2.3: `getTotal` returns sum of all item prices

4. Pair with a convergence loop

The test-plan-as-spec works best when paired with a convergence loop that works through criteria one at a time:

markdown
## Convergence Loop

On each iteration:

1. **Assess:** Run tests. Identify which criteria pass and which don't.
2. **Plan:** Pick the next failing criterion from the checklist.
3. **Edit:** Write the test from the criterion, then implement to pass.
4. **Mark Complete:** Check off the completed criterion.
5. **Repeat:** Continue until all criteria are checked.

What Makes This Different from TDD

TDD says "write the test first." This pattern says "write the test first and embed it in the spec so there is only one document." The differences:

AspectTDD aloneTest Plan as Spec
Where tests liveTest files onlySpec document + test files
Spec formatProse descriptionsExecutable test code
Drift riskSpec and tests can divergeImpossible — they are the same
CompletenessEasy to miss spec itemsEvery criterion has a test
OnboardingRead spec, then read testsRead one document

Verification Checks

  • Every acceptance criterion includes runnable test code
  • No criterion uses only prose — each has a code block
  • Criteria are organized into phases (API surface first, behavior second)
  • Phase ordering reflects build-up dependencies
  • Each criterion has a checkbox and task number
  • A convergence loop references the criteria checklist