The Problem
A developer tries TDD with an AI assistant for the first time. They write a spec, write tests, ask the AI to generate code. The code fails 3 tests. They feed the failures back to the AI. The AI fixes those 3 but breaks 2 others. They feed those back. The AI fixes those but introduces a new edge case failure. After 8 iterations, the developer gives up and writes the code manually.
This is a common experience -- but the problem is not TDD or the AI. The problem is an unstructured loop. The AI TDD loop needs rules: when to iterate, when to stop, how to feed failures back effectively, and when to escalate to manual intervention.
The 6-Step AI TDD Loop
┌─────────────────────────────────────────────────────────┐
│ │
│ 1. SPEC → Write the behavioral specification │
│ ↓ │
│ 2. TEST → Generate pytest tests from the spec │
│ ↓ │
│ 3. GENERATE→ Prompt AI with spec + tests for code │
│ ↓ │
│ 4. VERIFY → Run tests against AI-generated code │
│ ↓ │
│ 5. ITERATE → Feed failures back to AI for fixes │
│ ↓ │
│ 6. EDGE → Add edge-case tests the AI likely missed │
│ │
└─────────────────────────────────────────────────────────┘
Step 1: Spec
Write the behavioral specification (from Lesson 2). Include all 5 components: functional requirements, input domain, output contract, failure modes, and invariants.
Step 2: Test
Generate pytest test cases from the spec. Each spec component produces test cases:
- Functional requirements → happy path tests
- Input domain → edge case tests
- Output contract → return type and value tests
- Failure modes → error handling tests
- Invariants → property-based test stubs (Hypothesis)
Step 3: Generate
Prompt the AI with the spec and tests. The prompt structure:
Implement {function_name} according to this specification:
[spec]
The following tests must pass:
[tests]
Requirements:
- Handle all input domain cases
- Satisfy all invariants
- Never raise on valid inputs
Step 4: Verify
Run pytest against the AI-generated code. Record which tests pass and which fail.
Step 5: Iterate
For each failing test, feed the failure back to the AI:
The following test failed:
{test_name}: {inputs} → expected {expected}, got {actual}
Fix the implementation so this test passes without breaking other tests.
Critical rule: After each fix, re-run ALL tests, not just the failing ones. The AI may fix one test while breaking another.
Step 6: Edge Cases
After all spec-derived tests pass, add edge-case tests that the AI likely missed. These are tests you think of after seeing the implementation -- but written from the spec, not the implementation:
- Concurrency: "What if two threads call this simultaneously?"
- Resource limits: "What if the input is 10MB?"
- Type coercion: "What if the input is a string that looks like a number?"
- Unicode: "What if the input contains emoji or zero-width characters?"
The iteration limit rule
Set a maximum of 5 iterations. If the AI cannot pass all tests in 5 iterations, stop and analyze why. Common reasons: (1) the spec is ambiguous or contradictory, (2) the tests are testing implementation details rather than behavior, (3) the function is too complex for a single AI generation and should be decomposed. Do not iterate indefinitely -- the loop should converge, not cycle.
During the AI TDD loop, after iteration 3, the AI fixes all failing tests but introduces a new failure in a previously passing test. What should you do?
Regressions are common in the AI TDD loop. The AI fixes one defect but may introduce another because it does not have a holistic view of all test expectations. The correct approach is: (1) always run the full test suite after each AI fix, not just the failing tests, (2) feed the new failure back with context about what the previous fix was, and (3) ask the AI to fix both the original issue and the regression simultaneously. If regressions persist across multiple iterations, it may indicate the function is too complex and should be decomposed into smaller functions.