Agent Skills: TDD Bug Fix

Fix bugs using red-green-refactor — reproduce the bug as a failing test first, then fix it. Use when fixing bugs to ensure they never regress.

UncategorizedID: rshankras/claude-code-apple-skills/tdd-bug-fix

Install this agent skill to your local

pnpm dlx add-skill https://github.com/rshankras/claude-code-apple-skills/tree/HEAD/skills/testing/tdd-bug-fix

Skill Files

Browse the full folder contents for tdd-bug-fix.

Download Skill

Loading file tree…

skills/testing/tdd-bug-fix/SKILL.md

Skill Metadata

Name
tdd-bug-fix
Description
Fix bugs using red-green-refactor — reproduce the bug as a failing test first, then fix it. Use when fixing bugs to ensure they never regress.

TDD Bug Fix

Fix bugs the right way: reproduce first, fix second, verify always. Especially critical when using AI to generate fixes — the test ensures the AI actually solved the problem.

When This Skill Activates

Use this skill when the user:

  • Reports a bug and wants it fixed
  • Says "this is broken" or "this doesn't work"
  • Asks to "fix and add a test" or "fix with TDD"
  • Wants to ensure a bug doesn't come back
  • Is skeptical of AI-generated fixes ("how do I know it's actually fixed?")

Why TDD for Bug Fixes

Without TDD:           With TDD:
Bug reported           Bug reported
  → AI generates fix     → Write failing test (RED)
  → Deploy               → AI generates fix (GREEN)
  → Hope it works        → Test passes — confirmed fixed
  → Bug returns later    → Test prevents regression forever

Process

Phase 1: Understand the Bug

Gather information:

  1. What's the expected behavior?
  2. What's the actual behavior?
  3. Steps to reproduce?
  4. Which code is involved?
Grep: "[relevant keyword]" to find the source
Read: the suspected file(s)

Identify:

  • [ ] The function/method where the bug lives
  • [ ] The input that triggers the bug
  • [ ] The incorrect output/behavior
  • [ ] The correct expected output/behavior

Phase 2: RED — Write the Failing Test

Write a test that fails because of the bug. This proves the bug exists.

Template: Logic Bug

import Testing
@testable import YourApp

@Suite("Bug Fix: [Brief description]")
struct BugFix_DescriptionTests {

    @Test("should [expected behavior] — was [actual behavior]")
    func reproduceBug() {
        // Arrange — set up the conditions that trigger the bug
        let calculator = PriceCalculator()

        // Act — perform the operation that's broken
        let result = calculator.applyDiscount(price: 100, percent: 50)

        // Assert — what it SHOULD return (this will FAIL now)
        #expect(result == 50.0)  // Currently returns 0.5 (bug: divides by 100 twice)
    }
}

Template: Async Bug

@Test("should return cached items when network fails — was crashing")
func reproduceBug() async throws {
    let mockNetwork = MockNetworkClient(shouldFail: true)
    let mockCache = MockCache(items: [.sample])
    let service = DataService(network: mockNetwork, cache: mockCache)

    // This should return cached data, but currently crashes
    let items = try await service.fetchItems()

    #expect(items.count == 1)
}

Template: State Bug

@Test("should update count after deletion — was showing stale count")
func reproduceBug() {
    let manager = ItemManager()
    manager.addItem(Item(title: "Test"))

    manager.deleteItem(at: 0)

    #expect(manager.count == 0)     // Currently still returns 1
    #expect(manager.items.isEmpty)  // Currently still contains item
}

Template: Edge Case Bug

@Test("should handle empty input — was crashing with index out of range")
func reproduceBug() {
    let parser = CSVParser()

    // This crashes with empty string
    let result = parser.parse("")

    #expect(result.rows.isEmpty)
    #expect(result.columns.isEmpty)
}

Template: UI State Bug

@Test("should show error state when API fails — was showing infinite spinner")
func reproduceBug() async {
    let failingAPI = MockAPI(shouldFail: true)
    let viewModel = ListViewModel(api: failingAPI)

    await viewModel.loadData()

    #expect(viewModel.state == .error)  // Currently stuck on .loading
    #expect(viewModel.isLoading == false)
}

Run the test — it MUST fail. If it passes, you haven't reproduced the bug.

xcodebuild test -scheme YourApp \
  -only-testing "YourAppTests/BugFix_DescriptionTests"

Phase 3: GREEN — Fix the Bug

Now fix the code to make the test pass. The fix should be minimal — only change what's needed.

Fix Guidelines

  • Change as little code as possible to make the test pass
  • Don't refactor while fixing — that's the next step
  • Don't fix other issues you notice — file them separately
  • AI should target the specific test — give Claude the failing test as context
Prompt to Claude: "Here's a failing test that reproduces a bug.
Fix the source code to make this test pass without breaking
any existing tests."

Run the test — it MUST pass now.

# Run the bug fix test
xcodebuild test -scheme YourApp \
  -only-testing "YourAppTests/BugFix_DescriptionTests"

# Run ALL tests to check for regressions
xcodebuild test -scheme YourApp

Phase 4: REFACTOR (Optional)

If the fix introduced duplication or the code could be cleaner:

  1. Refactor only with all tests passing
  2. Run tests after each change
  3. Keep the bug fix test — it's now a permanent regression test

Phase 5: Verify Completeness

Checklist before marking the bug as fixed:

  • [ ] Failing test written that reproduces the bug
  • [ ] Fix implemented — test now passes
  • [ ] All existing tests still pass (no regressions)
  • [ ] Edge cases covered (empty, nil, boundary values)
  • [ ] Test is clearly named and documents the bug
  • [ ] Fix is minimal — no unrelated changes

Multiple Related Bugs

If a bug has multiple symptoms, write multiple tests:

@Suite("Bug Fix: Discount calculation errors")
struct BugFix_DiscountCalculationTests {

    @Test("50% discount on $100 should be $50")
    func fiftyPercentDiscount() {
        let calc = PriceCalculator()
        #expect(calc.applyDiscount(price: 100, percent: 50) == 50.0)
    }

    @Test("0% discount should return original price")
    func zeroDiscount() {
        let calc = PriceCalculator()
        #expect(calc.applyDiscount(price: 100, percent: 0) == 100.0)
    }

    @Test("100% discount should return 0")
    func fullDiscount() {
        let calc = PriceCalculator()
        #expect(calc.applyDiscount(price: 100, percent: 100) == 0.0)
    }

    @Test("discount on $0 should return $0")
    func zeroPrice() {
        let calc = PriceCalculator()
        #expect(calc.applyDiscount(price: 0, percent: 50) == 0.0)
    }
}

Output Format

## Bug Fix: [Description]

### Bug Summary
- **Expected**: [What should happen]
- **Actual**: [What was happening]
- **Root cause**: [Why it was broken]

### Test (RED)
```swift
// Failing test that reproduces the bug

Fix (GREEN)

File: path/to/file.swift:XX

// Before (buggy)
// After (fixed)

Verification

  • [x] Bug test fails before fix
  • [x] Bug test passes after fix
  • [x] All existing tests still pass
  • [x] Edge cases covered: [list]

Regression Prevention

Test added: Tests/BugFixes/BugFix_DescriptionTests.swift This test will catch any future regression of this bug.


## Common Pitfalls

| Pitfall | Problem | Solution |
|---------|---------|----------|
| Test passes before fix | You didn't reproduce the bug | Make assertions more specific |
| Fix breaks other tests | Fix was too broad | Revert and use smaller, targeted change |
| Test is too specific | Brittle, breaks on unrelated changes | Test behavior, not implementation details |
| Skipping the red step | No proof the test catches the bug | Always verify test fails first |
| Fixing multiple bugs at once | Can't isolate regressions | One bug = one test + one fix |

## References

- `testing/tdd-feature/` — for new features (not bug fixes)
- `testing/characterization-test-generator/` — for capturing existing behavior first
- `generators/test-generator/` — for general test generation