The Superpowers Test-Driven Development Skill: A Deep Dive
Most teams say they do test-driven development. Most teams also write the code first and the tests "right after" — and then ship. The superpowers test-driven development skill is what TDD looks like when nobody is allowed to round the corners: one iron law, a strict RED-GREEN-REFACTOR cycle, mandatory verification at every transition, and a table of your own excuses with the counterargument already written.
The law is printed at the top of the skill file in all caps:
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
Write the code before the test? Delete it. Start over. The skill is unusually violent about this: don't keep it "as reference," don't "adapt" it while writing tests, don't look at it. Delete means delete. That sounds dogmatic until you understand the single principle the whole skill hangs from:
If you didn't watch the test fail, you don't know if it tests the right thing.
What the skill does in the Superpowers workflow
In the Superpowers skill library, test-driven development is the default engine for any behavior change: new features, bug fixes, refactoring, anything that alters what the software does. It slots into the workflow at precise handoff points. The brainstorming and writing-plans skills produce a spec; executing-plans walks that spec one task at a time, and each task runs through this skill's cycle. When something breaks mid-task, systematic-debugging takes over — and its final phase hands straight back here, because a bug fix begins with a failing reproduction test. When the cycle says "verify," the verification-before-completion skill governs what counts as evidence.
The skill also draws a hard line on exceptions. TDD applies always to features, fixes, refactors, and behavior changes. The only escapes are throwaway prototypes, generated code, and configuration files — and even those require asking your human partner first. The skill anticipates what you will actually do under deadline pressure and names it: "Thinking 'skip TDD just this once'? Stop. That's rationalization."
The Red-Green cycle, step by step
The cycle has five stations, and the two "verify" stations are what separate this skill from the folk version of TDD:
RED → verify RED → GREEN → verify GREEN → REFACTOR → next. Skipping a verify station is skipping the part that makes the other stations mean anything.
RED: write one failing test
One behavior, clear name, real code — no mocks unless unavoidable. The skill contrasts a good RED test with a bad one using a retry helper:
// Good: one behavior, real code, name states the contract
test('retries failed operations 3 times', async () => {
let attempts = 0;
const operation = () => {
attempts++;
if (attempts < 3) throw new Error('fail');
return 'success';
};
const result = await retryOperation(operation);
expect(result).toBe('success');
expect(attempts).toBe(3);
});
The bad version mocks the operation three times and asserts toHaveBeenCalledTimes(3) — a test that proves the mock library works. The heuristics are brutal and simple: if "and" appears in the test name, split it. If you can't name the production change that would make the test fail, the test doesn't test anything yet.
Verify RED: watch it fail — correctly
This station is mandatory and has three exit checks: the test fails (it doesn't error out on a typo), the failure message is the one you expected, and it fails because the feature is missing. A test that passes immediately is testing behavior that already exists; a test that errors is not yet a test. You fix the test and re-run until it fails for the right reason.
GREEN: minimal code to pass
The simplest thing that satisfies the test — a three-iteration loop for the retry test, not a configurable engine with maxRetries, backoff, and onRetry options nobody asked for. No "while I'm in there" improvements. The test is the spec; you implemented the spec.
Verify GREEN: watch it pass — all of it
Your new test passes, and then the important half: the project's whole suite passes, with pristine output — no errors, no warnings. The skill is explicit that a green run of your one file is not a green suite: "A scope statement in your task bounds the deliverable, not your verification." Any failure the full run shows — even one you didn't cause — goes in your report by name. A red test you watched scroll past and didn't mention is, in the skill's words, a report falsified by omission.
REFACTOR: clean up, stay green
Only after green: remove duplication, improve names, extract helpers. No new behavior. Re-run the suite; you must stay green. Then write the next failing test.
A real example: the Tetris plugin's pause/resume bug
Here is the skill paying for itself on a small, unglamorous target: a WordPress Tetris plugin — a single script.js rendering to a canvas with WASD/P controls, score display, and a pause button. No test framework, no build step, "just a toy." By the rationalization table, it's "too simple to test."
A jsdom smoke test was built instead: construct a DOM with the plugin's expected elements, stub the canvas context and the AJAX dependencies, load the real script, and drive it with synthetic keyboard events while reading internal state (gamePaused, score, the current piece's position) through a debug readout. Every assertion was written before probing the behavior — pure RED-first.
The failures came fast, and they were the interesting kind:
- A guaranteed crash on every piece lock. The test that dropped a piece to the bottom asserted the game survives and spawns the next piece. It didn't:
playerDrop()callednewPiece(), a function that was never defined anywhere. The real constructor was namedspawnPiece(). Every manual playthrough so far had just been lucky enough — or short enough — to miss it. - The I-piece rotation crashed. A focused test rotated each of the seven tetrominoes. The I-piece — whose matrix is 1×4, non-square — threw immediately:
rotateMatrix()indexedmatrix[row.length - 1 - x][y], which for a non-square matrix walks intomatrix[-1]. The fix was a proper column-wise transpose. No amount of "looks fine" would have caught this; one assertion per piece caught it in seconds. - The pause/resume false alarm — and why verify-RED matters. The test pressed the pause button and asserted the button's label flipped to "Resume." The label stayed stale. Alarm: a pause/resume bug! But the skill's discipline says a failing test must fail for a known reason — so the reproduction was probed before any "fix" was written. Calling
togglePause()directly flipped the label correctly; the internalgamePausedstate toggled fine. The defect was in the harness: this jsdom version's synthetic.click()/MouseEventdispatch doesn't reliably reachaddEventListenerhandlers. A hotfix to the pause logic would have "fixed" nothing and poisoned the test suite with a lie.
That third finding is the whole thesis of the skill in miniature. The verify-RED station — confirm the test fails for the expected reason — is what turned "pause is broken!" into "my test wasn't actually exercising the handler." The same instinct applies in both directions: the first two bugs were real and are now permanently pinned by regression tests, in a plugin nobody thought was worth testing.
When TDD applies beyond code
The skill's exception list says "configuration files" gets a pass — with your human partner's approval. But treating infrastructure as untestable is how 3 a.m. deploys happen, and the same RED-GREEN muscle transfers cleanly once you accept that a config's "unit test" is a command that exits non-zero.
Docker-compose validation. Before docker compose up, write the assertions you wish existed: docker compose config must parse the merged file; a grep-style check that every long-syntax service declares a restart policy; a check that no port binding collides with the ones already in use; a smoke check after up that every expected container reports Up. RED first means watching that check fail against a deliberately broken edit — add a tab-indent to the YAML, watch the validator catch it — before trusting the validator to protect a good deploy.
nginx configuration. The RED test for a new vhost is a curl that fails before the config exists: curl -sf http://newsite.test/health exits non-zero; write the server block; run nginx -t; reload; re-run curl; green. Redirect and header rules get the same treatment — assert curl -sI shows the 301 and the Strict-Transport-Security header — because "the config looks right" is exactly the claim verification-before-completion refuses to accept.
When NOT to use TDD
The honest answer is narrower than engineers would like. The skill's legitimate exceptions: throwaway prototypes you will delete, machine-generated code, and genuine exploratory spikes — and the spike rule has teeth: "Need to explore first? Fine. Throw away the exploration, start with TDD." The exploration answers the design question; the committed code still gets tests-first.
What does not qualify: "too simple to test" (simple code breaks; the test takes thirty seconds — ask the Tetris plugin where newPiece() was defined), "I'll test after" (tests written after pass immediately, which proves nothing — you never watched them catch anything), "I already manually tested it" (no record, not re-runnable, evaporates under pressure), and "keep the code as reference" (you'll adapt it — that's tests-after wearing a costume). Sunk-cost is the one worth memorizing: "already spent three hours deleting is wasteful" confuses the hours, which are gone either way, with the confidence, which you can still buy by rewriting test-first.
And the reframe the skill saves for last: hard-to-test usually means hard-to-use. When you must mock everything, the design is too coupled — that's a design signal, not a TDD failure.
Related Deep Dives in the Series
The superpowers skills are designed as a relay, and test-driven development sits between two runners. The systematic-debugging skill ends where this one begins: after four phases of root-cause work, its implementation step is "write a failing test that reproduces the bug," then one minimal fix, then verification — TDD as the proof that a fix is a fix. The verification-before-completion skill polices the finish line: verify GREEN means a fresh suite run with pristine output, not a memory of a run or a partial one, because the gap between "the tests should pass now" and "I ran them, here's the output" is where broken ships. Together the three skills form one loop: understand why it broke, make it fail on purpose, prove it passes in front of witnesses.
Tests-first is not a ritual for pure believers. It is the cheapest machine ever built for turning "I think it works" into "here is the failure it refuses to accept" — for application code, for a compose file, for an nginx vhost. Watch it fail. Then trust it. Browse the rest of the series in The Skills We Use index.
