Skip to content

refactor: reframe TDD's testing-anti-patterns as writing-good-tests#1935

Draft
obra wants to merge 6 commits into
devfrom
tdd-writing-good-tests
Draft

refactor: reframe TDD's testing-anti-patterns as writing-good-tests#1935
obra wants to merge 6 commits into
devfrom
tdd-writing-good-tests

Conversation

@obra

@obra obra commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Note for @arittr: hold merge until this has been run through evals.
The micro-tests below are sanity checks, not the eval pass.

Who is submitting this PR? (required)

Field Value
Your model + version Claude Fable 5 (claude-fable-5)
Harness + version Claude Code CLI 2.1.199
All plugins installed superpowers 6.1.0, claude-session-driver 4.0.0, episodic-memory, superpowers-chrome, context7, linear, plugin-dev, agent-sdk-dev
Human partner who reviewed this diff Jesse Vincent (@obra) — directed the reframe; full-diff review pending (hence draft)

What problem are you trying to solve?

test-driven-development's disclosure doc was a catalog of don'ts
("Testing Anti-Patterns", violation-first examples, three NEVER Iron
Laws). House direction is to phrase instructions as what to do —
"don't think of an elephant" framing leads with the wrong pattern and
buries the right one under it.

What does this PR change?

testing-anti-patterns.md becomes writing-good-tests.md (git mv,
history preserved). Same six content units, positively named and
GOOD-example-first: Assert on Real Behavior, Keep Test Cleanup in Test
Utilities, Mock at the Right Level, Mirror Real Data Completely, Tests
Ship With the Implementation, Prefer Real Components Over Complex Mocks.
Violations stay as contrast cases under each rule. All gate functions,
the "your human partner" correction lines, and the warning-signs list
survive verbatim or near-verbatim; the Iron Laws rephrase positive-first;
the Quick Reference flips from Anti-Pattern/Fix to When-you/Do. The
Bottom Line recap and the TDD-prevents-these section fold into one
Overview sentence. SKILL.md's pointer moves into its Good Tests section,
removing the separate Testing Anti-Patterns heading.

Is this change appropriate for the core library?

Yes — a reframe of an existing core reference doc; no scope change.

What alternatives did you consider?

  • Rename only, keep violation-first structure: rejected — the title
    was the symptom, the framing was the problem.
  • Merge the doc into SKILL.md: rejected — it is 240 lines of
    reference loaded on a specific trigger (adding mocks/test utilities);
    progressive disclosure is correct here.

Does this PR contain multiple unrelated changes?

No — one doc reframed, plus the pointer that references it.

Existing PRs

Environment tested

Harness (e.g. Claude Code, Cursor) Harness version Model Model version/ID
Claude Code CLI 2.1.199 Fable 5 (author), Sonnet (test subjects) claude-fable-5, claude-sonnet-5

New harness support (required if this PR adds a new harness)

N/A — no new harness.

Evaluation

  • Initial prompt: "test driven development has an 'anti-patterns'
    disclosure. I bet we want 'writing good tests' instead of
    anti-patterns."
  • Sanity checks after the change: two fresh subjects reading only the new
    doc, each under a concrete temptation. One faced a passing
    mock-existence assertion under time pressure — changed it to a
    real-behavior assertion citing Rule 1, including the judgment call that
    if nothing real remains to assert, the assertion dies rather than the
    mock check surviving. The other faced a test-only teardown method
    already written in the editor plus a "to be safe" mock of a method
    whose side effect the test asserted on — rejected both, citing Rules 2
    and 3, with correct test-utility and mock-at-lower-level alternatives.
  • Full before/after evals not yet run — that is the pre-merge gate for
    @arittr. The interesting eval question: does the positive frame hold
    the same line as the prohibition frame under stronger pressure?

Rigor

  • If this is a skills change: I used superpowers:writing-skills and
    completed adversarial pressure testing (paste results below)
  • This change was tested adversarially, not just on the happy path
  • I did not modify carefully-tuned content (Red Flags table,
    rationalizations, "human partner" language) without extensive evals
    showing the change is an improvement

Both temptation scenarios above are pressure arms (time pressure +
already-written code). Gate functions and human-partner lines carry over
intact; the reframe changes framing and ordering, not rules — and the
eval gate above is the check that the framing change is behavior-neutral
or better.

Human review

  • A human has reviewed the COMPLETE proposed diff before submission

Draft PR: opened for @obra's full-diff review and @arittr's eval pass; box
intentionally unchecked until that review happens.

obra added 6 commits July 5, 2026 12:49
The disclosure doc becomes a catalog of what to do: six positively named
rules (assert on real behavior, cleanup in test utilities, mock at the
right level, mirror real data, tests ship with implementation, prefer
real components), each leading with the GOOD example and keeping the
violation as contrast. Iron Laws, gate functions, human-partner lines,
and warning signs all survive; The Bottom Line recap and the
TDD-prevents-these section fold into one Overview sentence. SKILL.md's
pointer moves into the Good Tests section it belongs with. Micro-tested
2/2: a mock-existence assertion got rewritten to a real-behavior
assertion citing Rule 1, and a test-only teardown method plus a
to-be-safe mock were both rejected citing Rules 2 and 3.
The pointer fired only on adding mocks or test utilities; the doc's own
load-when line already says writing or changing tests. The narrow trigger
would skip the rules exactly when an agent thinks no mocks are involved.
Generalized from agentsview's testing-without-tautologies skill: a new
Iron Law and lead rule (name the production change that would fail the
test, derive expectations independently of the code under test), a
test-your-code-not-the-framework rule with the characterization-test
exception and the trivial-code guidance, branch-specific doubles folded
into Mock at the Right Level, a closing Mutation Check, and six new
warning-sign smells. Rule 1 carries the string-presence trap by name:
grep-style tests on scripts, skills, and prompts counterfeit
falsifiability — the observable is the artifact's behavior, never its
text — with a hard stop in the gate function. Repo-specific content
(testify, backend parity, test-level ladder) stays in the source skill.
Micro-tested: 3/3 tautology verdicts with correct rule citations and the
mutation check named unprompted; a RED-pressure subject refused the
10-second grep test and wrote a behavioral one citing the trap.
Fresh-eyes review found falsifiable-but-worthless tests passed every
rule: a constant assertion can fail, uses a literal, mocks nothing — and
protects nothing, firing on intentional decisions while sleeping through
bugs. Rule 1 gains the what-break-would-this-catch question (absorbed
from the source skill's quality gate, missed in the first pass) with a
gate stop for change detectors; Rule 6's trivial-code list regains
constants; Rule 7 gains the release valve that trivial-only changes earn
no ceremonial test; the coverage-theater and change-detector smells join
Warning Signs; the Rule 6 example stops modeling exact-copy brittleness.
Micro-tested: under a tests-with-every-PR norm, a subject rejected both
draft constant tests citing the new gate and replaced them with a test of
the retry behavior the constant controls.
…earn no tests

Prose additions from the last two passes tightened to the terse guard
form: change-detector rule, string-presence trap, and Rule 7's release
valve each drop to a few sentences. Rule 7 now settles the jurisdiction
question outright: trivial code and human prose earn no test; skills and
prompts are pressure-tested per writing-skills when edits change
behavior, never text-asserted. Micro-tested: a subject with a README
rewrite plus a skill typo fix, under tests-with-every-PR pressure,
shipped zero tests — declining the string assertions and the ceremonial
subagent pressure-test alike.
Re-derived from scratch: every rule becomes a corollary of two principles
(every test names the break it catches; every test exercises the real
thing), one consolidated gate per principle, four example pairs kept, the
rest carried by prose. Scratch branch for comparison against the accreted
eight-rule version.
@obra

obra commented Jul 5, 2026

Copy link
Copy Markdown
Owner Author

Final shape swap: the head commit (0e69a4d) replaces the accreted eight-rule structure with a ground-up two-principle rewrite — every test names the break it catches / every test exercises the real thing — with the rules as corollaries, one gate function per principle, and four example pairs. 1,310 words vs 2,094 (−37%).

Parity-checked 3/3 against the same temptation scenarios as the earlier structure (grep-a-script under RED pressure, constant change-detector under tests-with-every-PR, README+skill-typo doc changes). Notable: subjects reasoning against the rewrite composed conclusions the flat version had to state explicitly — one rejected a ceremonial pressure-test of a typo fix by deriving it would be 'a change detector with guaranteed-pass.'

For the eval pass: the intermediate flat-structure version survives in history at 8afa64b if a head-to-head shape comparison is wanted; commit-level history documents every hole found and closed along the way (string trap, change detector, doc jurisdiction).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant