How to Implement Test-Driven Development with Claude Code TDD Skill
Purpose
This post shows how to enforce test-driven development using Claude Code’s test-driven-development skill. The key point is that Claude writes tests first, then implementation, making TDD practice automatic.
The Problem
I know TDD is good practice. I’ve read the books. I’ve watched the talks. But when I sit down to code, I fall into the same trap.
Here’s what usually happens:
My brain: "Let me just write the function first, then I'll add tests."30 minutes later: "The tests can wait until tomorrow."Next week: Tests still don't exist.The red-green-refactor cycle sounds great in theory. In practice, I skip the “red” phase entirely. I write code first, then backfill tests that pass trivially.
This defeats TDD’s purpose. Tests written after implementation rarely drive design. They just confirm what I already built.
I wanted a system that would force me to write tests first. Not through willpower, but through automation.
The Solution
Claude Code has a skill called test-driven-development. It enforces the red-green-refactor cycle automatically.
Installation
First, I installed the skill:
npx skills add obra/superpowers@test-driven-development -y -gThe -g flag installs it globally, so it works across all my projects.
How It Works
When I request a feature, the skill forces this sequence:
- RED - Write a failing test first
- GREEN - Write minimal code to pass the test
- REFACTOR - Improve code while keeping tests green
Let me show you a real example.
Example: Email Validation
I asked Claude to implement email validation using TDD:
"Using TDD, implement a function that validates email addresses.It should reject invalid formats and accept valid ones."Step 1: RED Phase
Claude started by writing failing tests:
describe('validateEmail', () => { it('should reject invalid emails', () => { expect(validateEmail('invalid')).toBe(false); expect(validateEmail('no@sign')).toBe(false); expect(validateEmail('@nodomain.com')).toBe(false); });
it('should accept valid emails', () => { });});Notice: no implementation exists yet. Claude ran the tests and they failed:
ReferenceError: validateEmail is not definedThis is the “red” in red-green-refactor. A failing test proves the test actually tests something.
Step 2: GREEN Phase
Claude then wrote the minimal implementation:
function validateEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);}
module.exports = { validateEmail };Tests passed:
PASS email.test.js validateEmail ✓ should reject invalid emails (3ms) ✓ should accept valid emails (1ms)Step 3: REFACTOR Phase
Claude suggested improvements:
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function validateEmail(email) { if (typeof email !== 'string') { return false; } return EMAIL_REGEX.test(email);}
// Added edge case handlingfunction validateEmailStrict(email) { if (!validateEmail(email)) return false; const [localPart, domain] = email.split('@'); return localPart.length <= 64 && domain.length <= 255;}
module.exports = { validateEmail, validateEmailStrict };Tests still passed after refactoring. This is crucial. The test safety net lets me refactor confidently.
Why This Matters
TDD produces better code. Here’s what I noticed after using this skill for a week:
Better Design: Writing tests first forces me to think about interfaces. I design from the outside in, which produces cleaner APIs.
Fewer Bugs: Tests catch edge cases I would miss. The email validation example above now handles non-string inputs, which I initially forgot.
Confident Refactoring: With tests passing, I can improve code structure without fear. I know immediately if I break something.
Documentation: Tests serve as living documentation. Six months later, I can read the tests and understand what the code should do.
Common Mistakes
I made several mistakes when starting with this skill. Here’s what I learned:
1. Skipping the RED Phase
Sometimes I got impatient and asked Claude to write tests and implementation together:
# WRONG - Asking for both at once"Write a validateEmail function with tests"This defeats TDD. Claude might write trivial tests that always pass. The skill works because it forces the red phase first.
The correct approach:
# CORRECT - Explicitly invoke TDD"Using TDD, implement email validation"2. Writing Trivial Tests
Early on, I accepted tests like this:
it('should work', () => {});This tests one happy path. It doesn’t verify behavior boundaries.
Better tests cover edge cases:
describe('validateEmail', () => { it('should reject empty string', () => { expect(validateEmail('')).toBe(false); });
it('should reject null', () => { expect(validateEmail(null)).toBe(false); });
it('should reject undefined', () => { expect(validateEmail(undefined)).toBe(false); });
it('should reject spaces in email', () => { expect(validateEmail('test @example.com')).toBe(false); });});3. Refactoring Without Re-running Tests
After refactoring, I sometimes forgot to run tests. This is dangerous. Refactoring can introduce subtle bugs.
The skill reminds me to run tests after each refactor. I now make it a habit:
npm test -- --watchRunning tests in watch mode during refactoring gives immediate feedback.
Integration with Existing Workflow
The TDD skill integrates with my development workflow:
With CI/CD
I added test coverage requirements to my CI pipeline:
name: Teston: [push, pull_request]jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci - run: npm test -- --coverage - name: Check coverage run: | coverage=$(cat coverage/coverage-summary.json | jq '.total.lines.pct') if (( $(echo "$coverage < 80" | bc -l) )); then echo "Coverage $coverage% is below 80%" exit 1 fiThis ensures TDD-produced code meets coverage standards.
With Pre-commit Hooks
I use husky to run tests before commits:
{ "husky": { "hooks": { "pre-commit": "npm test" } }}Now I can’t commit without passing tests.
Comparison: TDD Skill vs Manual TDD
Here’s how the TDD skill compares to manual TDD:
| Aspect | Manual TDD | TDD Skill |
|---|---|---|
| Discipline required | High | Low |
| Forgetting tests | Common | Rare |
| Test-first enforcement | Willpower-based | Automatic |
| Consistency | Varies by day | Always enforced |
The skill removes the willpower component. TDD becomes the default behavior.
Summary
In this post, I showed how to use Claude Code’s test-driven-development skill to enforce TDD practice. The key point is that the skill automates the discipline of writing tests first.
Next steps:
- Install the skill:
npx skills add obra/superpowers@test-driven-development -y -g - Try it on your next feature with: “Using TDD, implement [feature]”
- Watch Claude write failing tests first, then implementation
- Refactor with confidence knowing tests protect you
This transforms TDD from a good intention into an automatic practice.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments