Use when implementing features or fixing bugs - enforces RED-GREEN-REFACTOR cycle requiring tests to fail before writing code
<skill_overview> Write the test first, watch it fail, write minimal code to pass. If you didn't watch the test fail, you don't know if it tests the right thing. </skill_overview>
<rigidity_level> LOW FREEDOM - Follow these exact steps in order. Do not adapt.
Violating the letter of the rules is violating the spirit of the rules. </rigidity_level>
<quick_reference>
| Phase | Action | Command Example | Expected Result |
|-------|--------|-----------------|-----------------|
| RED | Write failing test | cargo test test_name | FAIL (feature missing) |
| Verify RED | Confirm correct failure | Check error message | "function not found" or assertion fails |
| GREEN | Write minimal code | Implement feature | Test passes |
| Verify GREEN | All tests pass | cargo test | All green, no warnings |
| REFACTOR | Clean up code | Improve while green | Tests still pass |
Iron Law: NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
</quick_reference>
<when_to_use> Always use for:
Ask your human partner for exceptions:
Thinking "skip TDD just this once"? Stop. That's rationalization. </when_to_use>
<the_process>
Write one minimal test showing what should happen.
Requirements:
See resources/language-examples.md for Rust, Swift, TypeScript examples.
MANDATORY. Never skip.
Run the test and confirm:
If test passes: You're testing existing behavior. Fix the test. If test errors: Fix syntax error, re-run until it fails correctly.
Write simplest code to pass the test. Nothing more.
Key principle: Don't add features the test doesn't require. Don't refactor other code. Don't "improve" beyond the test.
MANDATORY.
Run tests and confirm:
If test fails: Fix code, not test. If other tests fail: Fix now before proceeding.
Only after green:
Keep tests green. Don't add behavior.
Next failing test for next feature.
</the_process>
<examples> <example> <scenario>Developer writes implementation first, then adds test that passes immediately</scenario> <code> // Code written FIRST def validate_email(email): return "@" in email # Bug: accepts "@@"// Test written AFTER def test_validate_email(): assert validate_email("user@example.com") # Passes immediately! // Missing edge case: assert not validate_email("@@") </code>
<why_it_fails> When test passes immediately:
Tests written after verify remembered cases, not required behavior. </why_it_fails>
<correction> **TDD approach:**def test_validate_email():
assert validate_email("user@example.com") # Will fail - function doesn't exist
assert not validate_email("@@") # Edge case up front
NameError: function 'validate_email' is not defined
def validate_email(email):
return "@" in email and email.count("@") == 1
Result: Test failed first, proving it works. Edge case discovered during test writing, not in production. </correction> </example>
<example> <scenario>Developer has already written 3 hours of code without tests. Wants to keep it as "reference" while writing tests.</scenario> <code> // 200 lines of untested code exists // Developer thinks: "I'll keep this and write tests that match it" // Or: "I'll use it as reference to speed up TDD" </code><why_it_fails> Keeping code as "reference":
Result: All the problems of test-after, none of the benefits of TDD. </why_it_fails>
<correction> **Delete it. Completely.**git stash # Or delete the file
Then start TDD:
Why delete:
What you gain:
<why_it_fails> "Test is hard" is valuable signal:
Implementing first ignores this signal:
Hard to test? Simplify the interface:
// Instead of:
class UserService {
init(db: Database, email: EmailService, payments: PaymentGateway, logger: Logger) { }
func createAccount(email: String, password: String, paymentToken: String) throws { }
}
// Make testable:
class UserService {
func createAccount(request: CreateAccountRequest) -> Result<Account, Error> {
// Dependencies injected through request or passed separately
}
}
Test becomes simple:
func testCreatesAccountFromRequest() {
let service = UserService()
let request = CreateAccountRequest(email: "user@example.com")
let result = service.createAccount(request: request)
XCTAssertEqual(result.email, "user@example.com")
}
TDD forces good design. If test is hard, fix design before implementing. </correction> </example>
</examples><critical_rules>
Write code before test? → Delete it. Start over.
Test passes immediately? → Not TDD. Fix the test or delete the code.
Can't explain why test failed? → Fix until failure makes sense.
Want to skip "just this once"? → That's rationalization. Stop.
All of these mean: Stop, follow TDD:
</critical_rules>
<verification_checklist>
Before marking work complete:
Can't check all boxes? You skipped TDD. Start over.
</verification_checklist>
<integration>This skill calls:
This skill is called by:
Agents used:
Detailed language-specific examples:
When stuck:
npx skills add withzombies/test-driven-development下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
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