Claude
Skills
Sign in
Back

tdd-constraints

Included with Lifetime
$97 forever

Red-green-domain TDD cycle with strict phase boundaries and domain modeling review

Code Reviewtddtest-driven-developmentdomain-modelingworkflow

What this skill does


# TDD Constraints

**Version:** 1.0.0
**Portability:** Universal

---

## Detected Project Environment

!`if [ -f Cargo.toml ]; then echo "Language: Rust | Test runner: cargo test"; elif [ -f package.json ]; then if grep -q vitest package.json 2>/dev/null; then echo "Language: TypeScript/JavaScript | Test runner: npx vitest"; elif grep -q jest package.json 2>/dev/null; then echo "Language: TypeScript/JavaScript | Test runner: npx jest"; else echo "Language: TypeScript/JavaScript | Test runner: npm test"; fi; elif [ -f pyproject.toml ] || [ -f setup.py ]; then echo "Language: Python | Test runner: pytest"; elif [ -f go.mod ]; then echo "Language: Go | Test runner: go test ./..."; elif [ -f mix.exs ]; then echo "Language: Elixir | Test runner: mix test"; else echo "Language: unknown | Test runner: unknown (configure manually)"; fi`

## Objective

Defines a disciplined TDD workflow with three distinct phases (Red, Green, Domain) and strict boundaries between them. Enforces domain modeling review at natural checkpoints to prevent primitive obsession and invalid state representation.

**Purpose:** Ensure tests drive design, maintain clear separation of concerns, and build domain-rich implementations from the start.

**Scope:**
- **Included:** RED/GREEN/DOMAIN phases, file ownership patterns, phase transitions, domain modeling principles, rationalization red flags
- **Excluded:** Specific test frameworks, language-specific syntax, tool-specific workflows

---

## Core Principles

### Principle 1: Three-Phase Cycle (Red → Domain → Green → Domain)

**The Cycle:** Every feature is built through alternating phases with domain review at natural checkpoints.

**Why this matters:** Traditional TDD (red → green → refactor) often skips domain modeling, leading to primitive obsession and anemic models. Adding domain review phases catches these issues early.

**The Four Steps:**
1. **RED:** Write one failing test
2. **DOMAIN (after red):** Review test, create type definitions
3. **GREEN:** Implement minimal code to pass test
4. **DOMAIN (after green):** Review implementation for domain violations

**How to apply:**
- Complete each phase fully before moving to the next
- Don't skip domain reviews ("we'll model it later")
- Each phase has a single, focused responsibility
- Repeat cycle for each acceptance criterion

**Example Cycle:**
```
RED: Write test for User.authenticate(email, password)
  ↓
DOMAIN: Review test, create Email type (not String), Password type, AuthError enum
  ↓
GREEN: Implement User.authenticate() using domain types
  ↓
DOMAIN: Review implementation, check for primitive obsession
  ↓
CYCLE COMPLETE (move to next test)
```

### Principle 2: Strict Phase Boundaries (File Ownership)

**The Principle:** Each phase can only edit specific file types. This creates mechanical enforcement of separation of concerns.

**Why this matters:** Without boundaries, developers drift into "test and implement simultaneously" which defeats TDD's design benefits. Mechanical boundaries prevent drift.

**Phase Ownership:**

**RED Phase - Test Files Only**
- CAN edit: Test files (`*_test.*`, `*.test.*`, `tests/`, `spec/`)
- CANNOT edit: Production code, type definitions, configuration

**DOMAIN Phase - Type Definitions Only**
- CAN edit: Type definitions (structs, enums, interfaces, traits)
- CAN create: Stubs with `unimplemented!()`, `todo!()`, `raise NotImplementedError`
- CANNOT edit: Test logic, implementation bodies

**GREEN Phase - Implementation Bodies Only**
- CAN edit: Function/method implementations, filling in stubs
- CANNOT edit: Test files, type definitions (signatures)

**How to apply:**
- Enforce mechanically (hooks, pre-commit checks, linters)
- If blocked by boundary, STOP and return to workflow coordinator
- Never circumvent boundaries ("just this once")

**Example:**
```rust
// RED phase writes test (test file only)
#[test]
fn user_can_authenticate_with_valid_credentials() {
    let user = User::new(Email::from("[email protected]"), Password::from("secret"));
    let result = user.authenticate("secret");
    assert!(result.is_ok());
}

// DOMAIN phase creates types (type definition files only)
struct User { email: Email, password_hash: PasswordHash }
struct Email(String);  // Newtype
struct Password(String); // Newtype
enum AuthError { InvalidPassword, UserNotFound }

impl User {
    fn new(email: Email, password: Password) -> Self {
        unimplemented!("GREEN phase will implement")
    }

    fn authenticate(&self, password: &str) -> Result<(), AuthError> {
        unimplemented!("GREEN phase will implement")
    }
}

// GREEN phase implements bodies (implementation only)
impl User {
    fn new(email: Email, password: Password) -> Self {
        User {
            email,
            password_hash: PasswordHash::from(password)
        }
    }

    fn authenticate(&self, password: &str) -> Result<(), AuthError> {
        if self.password_hash.verify(password) {
            Ok(())
        } else {
            Err(AuthError::InvalidPassword)
        }
    }
}

// DOMAIN phase reviews: ✓ No primitive obsession, types are meaningful
```

### Principle 3: One Assertion Per Test (Red Phase Constraint)

**The Principle:** Each test verifies exactly one behavior with one assertion.

**Why this matters:** Multiple assertions hide which expectation failed. When a test with 5 assertions fails, you don't know which of the 5 expectations is wrong without investigating.

**How to apply:**
- Write one test with one `assert`
- If you need multiple verifications, write multiple tests
- If setup is complex, extract helper functions

**Example:**
```python
# ❌ Bad: Multiple assertions (which one fails?)
def test_user_registration():
    user = register_user("alice", "[email protected]", "password123")
    assert user.name == "alice"
    assert user.email == "[email protected]"
    assert user.password is not None
    assert user.is_active == True
    assert user.created_at is not None

# ✓ Good: One assertion per test (failure is obvious)
def test_user_registration_sets_name():
    user = register_user("alice", "[email protected]", "password123")
    assert user.name == "alice"

def test_user_registration_sets_email():
    user = register_user("alice", "[email protected]", "password123")
    assert user.email == "[email protected]"

def test_user_registration_hashes_password():
    user = register_user("alice", "[email protected]", "password123")
    assert user.password_hash is not None

def test_user_registration_activates_by_default():
    user = register_user("alice", "[email protected]", "password123")
    assert user.is_active == True
```

### Principle 4: Minimal Implementation (Green Phase Constraint)

**The Principle:** Write the simplest code that makes the test pass. No more, no less.

**Why this matters:** Over-engineering during green phase adds complexity that isn't tested. YAGNI (You Aren't Gonna Need It) prevents speculative complexity.

**How to apply:**
- Read the failing test
- Write just enough code to make it pass
- Don't add features "while you're here"
- Don't add error handling not required by tests
- Don't add flexibility for future use cases

**Example:**
```typescript
// Test (RED phase)
test('user can register with email', () => {
  const user = registerUser('[email protected]');
  expect(user.email).toBe('[email protected]');
});

// ❌ Bad: Over-engineered (not tested)
function registerUser(email: string, options?: {
  role?: 'admin' | 'user',
  metadata?: Record<string, any>,
  sendWelcomeEmail?: boolean
}): User {
  const user = new User(email);
  user.role = options?.role ?? 'user';
  user.metadata = options?.metadata ?? {};

  if (options?.sendWelcomeEmail !== false) {
    sendEmail(user.email, 'Welcome!');
  }

  return user;
}

// ✓ Good: Minimal (exactly what test requires)
function registerUser(email: string): User {
  return new User(email);
}
```

### Principle 5: Domain Modeling Review at Checkpoints

**The Principle:** After RED

Related in Code Review