Files
ai-development-scaffold/.claude/skills/testing/tdd/SKILL.md
James Bland befb8fbaeb feat: initial Claude Code configuration scaffold
Comprehensive Claude Code guidance system with:

- 5 agents: tdd-guardian, code-reviewer, security-scanner, refactor-scan, dependency-audit
- 18 skills covering languages (Python, TypeScript, Rust, Go, Java, C#),
  infrastructure (AWS, Azure, GCP, Terraform, Ansible, Docker/K8s, Database, CI/CD),
  testing (TDD, UI, Browser), and patterns (Monorepo, API Design, Observability)
- 3 hooks: secret detection, auto-formatting, TDD git pre-commit
- Strict TDD enforcement with 80%+ coverage requirements
- Multi-model strategy: Opus for planning, Sonnet for execution (opusplan)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 15:47:34 -05:00

350 lines
9.2 KiB
Markdown

---
name: tdd-workflow
description: Test-Driven Development workflow with RED-GREEN-REFACTOR cycle, coverage verification, and quality gates. Use when planning or implementing features with TDD.
---
# TDD Workflow Skill
## The TDD Cycle
```
┌─────────────────────────────────────────────────────┐
│ │
│ ┌─────┐ ┌───────┐ ┌──────────┐ │
│ │ RED │ ──▶ │ GREEN │ ──▶ │ REFACTOR │ ──┐ │
│ └─────┘ └───────┘ └──────────┘ │ │
│ ▲ │ │
│ └──────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
```
## Phase 1: RED (Write Failing Test)
### Rules
- Write ONE test that describes desired behavior
- Test must fail for the RIGHT reason
- NO production code exists yet
### Checklist
- [ ] Test describes behavior, not implementation
- [ ] Test name clearly states expected outcome
- [ ] Test uses factory functions for data
- [ ] Test fails with meaningful error message
- [ ] Error indicates missing behavior, not syntax/import errors
### Example
```typescript
// STEP 1: Write the failing test
describe('PaymentProcessor', () => {
it('should reject payments with negative amounts', () => {
const payment = getMockPayment({ amount: -100 });
const result = processPayment(payment);
expect(result.success).toBe(false);
expect(result.error).toBe('Amount must be positive');
});
});
// At this point:
// - processPayment doesn't exist
// - Running test gives: "ReferenceError: processPayment is not defined"
// - This is the RIGHT failure - the function doesn't exist yet
```
## Phase 2: GREEN (Write Minimum Code)
### Rules
- Write ONLY enough code to pass the test
- No additional functionality
- No "while you're there" improvements
- Ugly code is acceptable (we'll refactor)
### Checklist
- [ ] Implementation makes test pass
- [ ] No code beyond what test demands
- [ ] All existing tests still pass
- [ ] Coverage increased for new code
### Example
```typescript
// STEP 2: Write minimum code to pass
export function processPayment(payment: Payment): PaymentResult {
if (payment.amount < 0) {
return { success: false, error: 'Amount must be positive' };
}
return { success: true };
}
// This passes the test - that's enough for now
// Don't add validation for zero, max amounts, etc.
// Those need their own tests first!
```
## Phase 3: REFACTOR (Improve Code Quality)
### Rules
- COMMIT before refactoring
- Only refactor if it adds value
- All tests must stay green
- No new functionality
### When to Refactor
- Duplicate knowledge (not just similar code)
- Unclear names
- Complex conditionals
- Magic numbers/strings
### When NOT to Refactor
- Code is already clean
- Similarity is structural, not semantic
- Would add unnecessary abstraction
### Example
```typescript
// STEP 3: Assess and refactor
// BEFORE REFACTORING: git commit -m "feat: add negative amount validation"
// Assessment:
// - Magic number 0 could be a constant
// - Error message could be a constant
// - But... it's simple enough. Skip refactoring.
// If we had multiple validations:
const VALIDATION_ERRORS = {
NEGATIVE_AMOUNT: 'Amount must be positive',
ZERO_AMOUNT: 'Amount must be greater than zero',
EXCEEDS_LIMIT: 'Amount exceeds maximum limit',
} as const;
const MAX_PAYMENT_AMOUNT = 10000;
export function processPayment(payment: Payment): PaymentResult {
if (payment.amount < 0) {
return { success: false, error: VALIDATION_ERRORS.NEGATIVE_AMOUNT };
}
if (payment.amount === 0) {
return { success: false, error: VALIDATION_ERRORS.ZERO_AMOUNT };
}
if (payment.amount > MAX_PAYMENT_AMOUNT) {
return { success: false, error: VALIDATION_ERRORS.EXCEEDS_LIMIT };
}
return { success: true };
}
```
## Complete TDD Session Example
```typescript
// === CYCLE 1: Negative amounts ===
// RED: Write failing test
it('should reject negative amounts', () => {
const payment = getMockPayment({ amount: -100 });
const result = processPayment(payment);
expect(result.success).toBe(false);
});
// RUN: ❌ FAIL - processPayment is not defined
// GREEN: Minimal implementation
function processPayment(payment: Payment): PaymentResult {
if (payment.amount < 0) return { success: false };
return { success: true };
}
// RUN: ✅ PASS
// REFACTOR: Assess - simple enough, skip
// COMMIT: "feat: reject negative payment amounts"
// === CYCLE 2: Zero amounts ===
// RED: Write failing test
it('should reject zero amounts', () => {
const payment = getMockPayment({ amount: 0 });
const result = processPayment(payment);
expect(result.success).toBe(false);
});
// RUN: ❌ FAIL - expected false, got true
// GREEN: Add zero check
function processPayment(payment: Payment): PaymentResult {
if (payment.amount <= 0) return { success: false };
return { success: true };
}
// RUN: ✅ PASS
// REFACTOR: Assess - still simple, skip
// COMMIT: "feat: reject zero payment amounts"
// === CYCLE 3: Maximum amount ===
// RED: Write failing test
it('should reject amounts over 10000', () => {
const payment = getMockPayment({ amount: 10001 });
const result = processPayment(payment);
expect(result.success).toBe(false);
});
// RUN: ❌ FAIL - expected false, got true
// GREEN: Add max check
function processPayment(payment: Payment): PaymentResult {
if (payment.amount <= 0) return { success: false };
if (payment.amount > 10000) return { success: false };
return { success: true };
}
// RUN: ✅ PASS
// REFACTOR: Now we have magic number 10000
// COMMIT first: "feat: reject payments over maximum"
// Then refactor:
const MAX_PAYMENT_AMOUNT = 10000;
function processPayment(payment: Payment): PaymentResult {
if (payment.amount <= 0) return { success: false };
if (payment.amount > MAX_PAYMENT_AMOUNT) return { success: false };
return { success: true };
}
// RUN: ✅ PASS
// COMMIT: "refactor: extract max payment constant"
```
## Quality Gates
Before committing, verify:
```bash
# 1. All tests pass
npm test # or pytest, cargo test
# 2. Coverage meets threshold
npm test -- --coverage
# Verify: 80%+ overall
# 3. Type checking passes
npm run typecheck # or mypy, cargo check
# 4. Linting passes
npm run lint # or ruff, cargo clippy
# 5. No secrets in code
git diff --staged | grep -i "password\|secret\|api.key"
```
## Coverage Verification
**CRITICAL: Never trust claimed coverage - always verify.**
```bash
# Python
pytest --cov=src --cov-report=term-missing --cov-fail-under=80
# TypeScript
npm test -- --coverage --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80}}'
# Rust
cargo tarpaulin --fail-under 80
```
### Interpreting Coverage
| Metric | Meaning | Target |
|--------|---------|--------|
| Lines | % of lines executed | 80%+ |
| Branches | % of if/else paths taken | 80%+ |
| Functions | % of functions called | 80%+ |
| Statements | % of statements executed | 80%+ |
### Coverage Exceptions (Document!)
```python
# pragma: no cover - Reason: Debug utility only used in development
def debug_print(data):
print(json.dumps(data, indent=2))
```
```typescript
/* istanbul ignore next -- @preserve Reason: Error boundary for React */
if (process.env.NODE_ENV === 'development') {
console.error(error);
}
```
## Anti-Patterns
### Writing Test After Code
```
❌ WRONG:
1. Write all production code
2. Write tests to cover it
3. "I have 80% coverage!"
✅ CORRECT:
1. Write failing test
2. Write code to pass
3. Repeat until feature complete
```
### Testing Implementation
```typescript
// ❌ BAD: Tests implementation detail
it('should call validatePayment', () => {
const spy = jest.spyOn(service, 'validatePayment');
processPayment(payment);
expect(spy).toHaveBeenCalled();
});
// ✅ GOOD: Tests behavior
it('should reject invalid payments', () => {
const payment = getMockPayment({ amount: -1 });
const result = processPayment(payment);
expect(result.success).toBe(false);
});
```
### Writing Too Many Tests at Once
```
❌ WRONG:
1. Write 10 tests
2. Implement everything
3. All tests pass
✅ CORRECT:
1. Write 1 test
2. Make it pass
3. Refactor if needed
4. Repeat
```
### Skipping Refactor Assessment
```
❌ WRONG:
1. Test passes
2. Immediately write next test
3. Code becomes messy
✅ CORRECT:
1. Test passes
2. Ask: "Should I refactor?"
3. If yes: commit, then refactor
4. If no: continue
```
## Commit Messages
Follow this pattern:
```bash
# After GREEN (feature added)
git commit -m "feat: add payment amount validation"
# After REFACTOR
git commit -m "refactor: extract payment validation constants"
# Test-only changes
git commit -m "test: add edge cases for payment validation"
# Bug fixes (driven by failing test)
git commit -m "fix: handle null payment amount"
```