Test-Driven Development workflow. Write tests BEFORE implementation code.
Write the test first. Watch it fail. Write the minimum code to make it pass.
Core principle: If you did not watch the test fail, you do not know whether you are testing the right thing.
Violating the letter of these rules violates their spirit.
Always:
Exceptions (ask the user):
If you are thinking "I will skip TDD just this once" — stop. That is rationalization.
NO PRODUCTION CODE WITHOUT A FAILING TEST
Wrote code before the test? DELETE IT. Start over.
No exceptions:
Implement from scratch, starting from tests. Period.
RED (Write a failing test)
|
v
Fails correctly? --NO--> Fix the test
|
YES
|
v
GREEN (Write minimum code)
|
v
All tests pass? --NO--> Fix the code
|
YES
|
v
REFACTOR (Clean up)
|
v
Still green? --NO--> Revert the refactor
|
YES
|
v
Next test --> Return to RED
Write one minimal test that shows what should happen.
Good:
test('retries failed operations 3 times', async () => {
let attemptCount = 0;
const operation = () => {
attemptCount++;
if (attemptCount < 3) throw new Error('failed');
return 'success';
};
const result = await retry(operation);
expect(result).toBe('success');
expect(attemptCount).toBe(3);
});
Clear name, tests real behavior, tests one thing.
Bad:
test('retry works', async () => {
const mock = jest.fn()
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('success');
await retry(mock);
expect(mock).toHaveBeenCalledTimes(2);
});
Vague name, tests the mock instead of the code.
Requirements:
MANDATORY. Never skip.
npm test path/to/test.test.ts
Check:
Test passes? You are testing existing behavior. Fix the test.
Test errors? Fix the error, re-run until it fails correctly.
Write the simplest code that makes the test pass.
Good:
async function retry<T>(fn: () => Promise<T>): Promise<T> {
for (let i = 0; i < 3; i++) {
try {
return await fn();
} catch (e) {
if (i === 2) throw e;
}
}
throw new Error('unreachable');
}
Just enough to pass.
Bad:
async function retry<T>(
fn: () => Promise<T>,
options?: {
maxRetries?: number;
backoff?: 'linear' | 'exponential';
onRetry?: (attempt: number) => void;
}
): Promise<T> {
// YAGNI -- do not add things that were not asked for
}
Over-engineering.
Do not add features beyond the test. Do not refactor other code. Do not "improve" anything.
MANDATORY.
npm test path/to/test.test.ts
Check:
Test fails? Fix the code, not the test. Other tests fail? Fix them now.
Only after green:
Keep tests green. Do not add behavior.
Next failing test for the next feature.
| Quality | Good | Bad |
|---------|------|-----|
| Minimal | Tests one thing. If the name contains "and", split it. | test('validates email and domain and whitespace') |
| Clear | Name describes the behavior | test('test1') |
| Shows intent | Reveals the desired API | Hides what the code should do |
"I will write tests afterward to verify my work"
Tests written after pass immediately. Passing immediately proves nothing:
Test-first forces you to see the test fail, proving it actually tests something.
| Excuse | Reality | |--------|---------| | "No need to test, it is too simple" | Simple code breaks too. The test takes 30 seconds. | | "I will test later" | Tests that pass immediately prove nothing. | | "Tests written after serve the same purpose" | After = "what does this do?" Before = "what should it do?" | | "I already tested manually" | Ad-hoc is not systematic. No record, not repeatable. | | "Deleting X hours of work is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. | | "Keep it as reference, write test-first" | You will adapt it. That is test-after in disguise. Delete means delete. | | "I need to explore first" | Fine. Throw away the exploration, then start with TDD. | | "Test is hard = design is unclear" | Listen to the test. Hard to test = hard to use. | | "TDD slows me down" | TDD is faster than debugging. Pragmatic = test-first. | | "Manual testing is faster" | Manual testing does not prove edge cases. You re-test on every change. | | "Existing code has no tests" | You are improving. Add tests for the code you are changing. |
All of these mean: Delete the code. Restart with TDD.
Before marking work as complete:
Cannot check all boxes? You skipped TDD. Start over.
Bug found? Write a failing test that reproduces the bug. Follow the TDD cycle. The test proves the fix and prevents regression.
Never fix a bug without a test.
REQUIRED SKILL: systematic-debugging (when the test fails unexpectedly)
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer