rshankras/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.
npx skills add https://github.com/rshankras/claude-code-apple-skills --skill 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.
Use this skill when the user:
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
Gather information:
Grep: "[relevant keyword]" to find the source
Read: the suspected file(s)
Identify:
Write a test that fails because of the bug. This proves the bug exists.
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)
}
}
@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)
}
@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
}
@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)
}
@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"
Now fix the code to make the test pass. The fix should be minimal — only change what's needed.
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
If the fix introduced duplication or the code could be cleaner:
Checklist before marking the bug as fixed:
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("discount on $0 should return $0")
func zeroPrice() {
let calc = PriceCalculator()
#expect(calc.applyDiscount(price: 0, percent: 50) == 0.0)
}
}
## Bug Fix: [Description]
### Bug Summary
- **Expected**: [What should happen]
- **Actual**: [What was happening]
- **Root cause**: [Why it was broken]
### Test (RED)
// 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.
| 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 |
testing/tdd-feature/ — for new features (not bug fixes)testing/characterization-test-generator/ — for capturing existing behavior firstgenerators/test-generator/ — for general test generationTake rshankras/tdd-bug-fix from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.