Testing

/test

Write the test first, especially when the AI writes the code.

01

Separate claim from evidence

A fluent answer is not a calibrated verdict

Ask an AI agent to write a feature and assess its own result in one pass. Its explanation may sound equally assured whether the edge cases are covered or not. That is not lying. A language model generates a plausible response from the context it receives, and confidence in the wording is not a measured probability that the code is correct.

If the same context produces both implementation and test, the two can share one mistaken interpretation. The test may simply describe what the code already does. Writing the test first reduces that coupling. It creates an independently reviewable behavioural contract: given these inputs and conditions, this observable result must follow.

A failing test must fail for the right reason

Red is useful only when it proves the behavioural contract is absent, not when the test itself is broken.

  1. Write one observable contract Name input, boundary and expected outcome.
  2. Run the test before implementation Confirm it fails against current behaviour.
  3. Inspect the failure The message must point to the missing behaviour, not setup or syntax.
  4. Implement the smallest change Earn green without broad unrelated edits.
  5. Add an adjacent case Check the nearby boundary the first test could hide.

Red establishes the test can observe the gap. Green then becomes meaningful evidence.

The gate has two sidesA test that starts green proves nothing about the change. A test that fails for the wrong reason proves nothing about the requirement.
02

Make red informative

A failing test must fail for the right reason

Red alone is not evidence. A test can fail because the import path is wrong, the database is unavailable or the fixture, the prepared test data, is malformed. That is a wrong-reason failure: it tells you nothing about the behaviour under development. Read the error and make the test reach the intended decision point before asking the implementation to satisfy it.

This follows the same discipline as reproducing a bug before changing it. The first useful failure demonstrates that the contract can detect the missing or incorrect behaviour. When the same test later passes, you have evidence that this implementation changed that observable result.

03

A complete worked example

An expired invitation should stay expired

Suppose the requirement says: an invitation expires 24 hours after it is created. An expired token must be rejected, must not create a membership and must return the message "Invitation expired." Before changing the handler, write a test with a fixed clock, an invitation created 25 hours earlier and assertions for all three outcomes.

The first run fails because the test imports the handler from the wrong path. Fix the import and run again. Now the request succeeds and creates a membership. That is red for the right reason: the current behaviour violates the expiry contract. Implement the time check, rerun and get green. Then add two boundary cases. An invitation created 23 hours and 59 minutes earlier still succeeds exactly once, while one created exactly 24 hours earlier is rejected because the rule treats age greater than or equal to 24 hours as expired. These cases guard against solving expiry by rejecting everything or leaving the exact boundary ambiguous.

The sequence supports the claim that these cases behave as specified. It does not prove that 24 hours is a wise policy, that every timezone and clock boundary is covered or that the whole invitation system is secure. That is why "done" remains a claim supported by bounded evidence, not a universal state.

04

Same files, better evidence

Order changes what the green result can mean

The loop is small. State the observable contract. Review it independently of the implementation. Run it and confirm the intended failure. Make the smallest implementation change that turns it green. Add an adjacent case that would expose the easiest overfit. Then run the relevant wider suite.

This is harness engineering in miniature. The workflow separates a proposal from an executable check and lets a deterministic runner report the result. A human or separate verifier can inspect the contract, failure and implementation diff. Tests written after code still matter for regression coverage and legacy systems; they simply need extra scrutiny for assumptions copied from the implementation.

Test order changes what the evidence means

Writing the contract before the implementation separates observation from self-confirmation.

Implementation first

  • Build from an interpretation Hidden assumptions enter the code.
  • Write a test that matches it The test may simply describe what was built.
  • See immediate green No evidence the test could catch the original absence.

separate specification from solution

Contract first

  • State observable behaviour A reviewer can challenge it before code.
  • Confirm meaningful red The current system lacks the required behaviour.
  • Earn green plus adjacency Implementation satisfies the contract and nearby boundary.

Test-first is valuable when the contract is correct, observable and not merely implementation detail.

Order creates independenceThe test is not automatically right because it came first. Its advantage is that it can disagree with the implementation before the implementation exists.
05

The boundary of the method

A precise test can enforce the wrong decision

Test-first does not rescue a bad requirement. A test can encode an unfair policy, miss an accessibility need, trust an unrealistic mock, a simplified stand-in for a real dependency, or ignore performance and security. A suite also samples behaviour; it cannot examine every possible state. Product judgement, domain review, exploratory testing and production monitoring still belong around it.

As a student, I like the moment when a correct red result disproves my first assumption before it hardens into code. As a professional, I value the review trail: requirement, failing evidence, implementation and passing evidence remain visible to the next person. The test is not an oracle. It is a small contract that makes one important disagreement cheap to discover.