Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Red-green-refactor scaffold for building new features with TDD. Write failing tests first, then implement to pass. Use when building new features test-first.
.claude/skills/rshankras-tdd-feature/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-17 | ✗→✓ | ▲ Improved | 80% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 48% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 41% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 32% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 102% | 0% |
Build new features using the red-green-refactor cycle. Tests define the spec, AI generates the implementation, tests verify correctness.
Use this skill when the user:
Traditional: AI generates code → You hope it's correct → Ship → Find bugs
TDD with AI: You write tests (spec) → AI generates code to pass → Proven correctThe test is your acceptance criteria in code form. AI excels at going from failing test to passing implementation — it's a concrete, unambiguous target.
Before writing any code or tests, understand:
Sketch the public interface before writing tests:
swift// Example: Designing a FavoriteManager protocol FavoriteManaging { func add(_ item: Item) async throws func remove(_ item: Item) async throws func isFavorite(_ item: Item) -> Bool var favorites: [Item] { get } var count: Int { get } }
This doesn't need to compile yet — it's the contract you'll test against.
Write tests for each behavior. Start with the simplest case and build up.
swiftimport Testing @testable import YourApp @Suite("FavoriteManager") struct FavoriteManagerTests { // 1. Construction @Test("starts with empty favorites") func startsEmpty() { let manager = FavoriteManager() #expect(manager.favorites.isEmpty) #expect(manager.count == 0) } // 2. Happy path @Test("can add a favorite") func addFavorite() async throws { let manager = FavoriteManager() let item = Item(id: "1", title: "Test") try await manager.add(item) #expect(manager.count == 1) #expect(manager.isFavorite(item)) } // 3. State verification @Test("can remove a favorite") func removeFavorite() async throws { let manager = FavoriteManager() let item = Item(id: "1", title: "Test") try await manager.add(item) try await manager.remove(item) #expect(manager.count == 0) #expect(!manager.isFavorite(item)) } // 4. Edge cases @Test("adding duplicate does not increase count") func addDuplicate() async throws { let manager = FavoriteManager() let item = Item(id: "1", title: "Test") try await manager.add(item) try await manager.add(item) #expect(manager.count == 1) } @Test("removing non-existent item does nothing") func removeNonExistent() async throws { let manager = FavoriteManager() let item = Item(id: "1", title: "Test") try await manager.remove(item) #expect(manager.count == 0) } // 5. Error handling @Test("throws when storage is full") func storageFullError() async { let manager = FavoriteManager(maxCapacity: 2) let items = (1...3).map { Item(id: "\($0)", title: "Item \($0)") } await #expect(throws: FavoriteError.capacityExceeded) { for item in items { try await manager.add(item) } } } // 6. Ordering @Test("favorites are in insertion order") func insertionOrder() async throws { let manager = FavoriteManager() let items = ["C", "A", "B"].map { Item(id: $0, title: $0) } for item in items { try await manager.add(item) } #expect(manager.favorites.map(\.title) == ["C", "A", "B"]) } }
Run tests — they should ALL fail (the type doesn't even exist yet).
Now implement the feature. Pass the tests as context to AI:
Prompt to Claude: "Here are my failing tests for FavoriteManager.
Implement the FavoriteManager class to make all tests pass.
Follow the protocol FavoriteManaging."bashxcodebuild test -scheme YourApp \ -only-testing "YourAppTests/FavoriteManagerTests"
With all tests green, clean up the implementation:
Run tests after every refactor step. If any test fails, you've changed behavior — revert.
Once the unit is solid, write integration tests:
swift@Suite("FavoriteManager Integration") struct FavoriteManagerIntegrationTests { @Test("persists favorites across sessions") func persistence() async throws { let store = InMemoryStore() // Session 1: Add favorite let manager1 = FavoriteManager(store: store) try await manager1.add(Item(id: "1", title: "Test")) // Session 2: Verify it persists let manager2 = FavoriteManager(store: store) await manager2.loadFavorites() #expect(manager2.count == 1) } }
RED → Write one failing test (30 seconds - 2 minutes)
GREEN → Make it pass with simplest code (1 - 5 minutes)
REFACTOR → Clean up while tests stay green (1 - 3 minutes)
REPEAT → Next testCadence matters. If you're spending more than 5 minutes on GREEN, the test might be too big. Break it into smaller tests.
swift@Suite("SearchViewModel") struct SearchViewModelTests { @Test("starts in idle state") @Test("searching updates state to loading") @Test("successful search shows results") @Test("empty search shows empty state") @Test("failed search shows error") @Test("debounces rapid input") @Test("cancels previous search on new input") }
swift@Suite("ItemRepository") struct ItemRepositoryTests { @Test("fetches items from remote") @Test("caches fetched items locally") @Test("returns cached items when offline") @Test("syncs local changes to remote") @Test("handles conflict resolution") @Test("deletes expire cached items") }
swift@Suite("SubscriptionManager") struct SubscriptionManagerTests { @Test("free user has basic access") @Test("pro user has full access") @Test("expired subscription reverts to free") @Test("family member inherits subscription") @Test("trial period grants pro access") @Test("grace period maintains access after lapse") }
markdown## TDD Feature: [Feature Name] ### API Design
// Protocol / public interface
### Tests Written (RED)
1. `startsEmpty` — Initial state
2. `addFavorite` — Happy path
3. `removeFavorite` — State change
4. `addDuplicate` — Edge case
5. `removeNonExistent` — Edge case
6. `storageFullError` — Error handling
### Implementation (GREEN)
**File**: `Sources/Features/FavoriteManager.swift`
All [X] tests passing.
### Refactoring Done
- Extracted storage logic to private method
- Renamed internal property for clarity
### Next Steps
- [ ] Add integration tests with persistence
- [ ] Wire up to ViewModel
- [ ] Add UI for favorite toggle| Pitfall | Problem | Solution | |---------|---------|----------| | Writing too many tests before implementing | Overwhelming; can't see progress | Write 2-3 tests, implement, repeat | | Tests that test implementation | Brittle; break on refactor | Test behavior and outcomes only | | Skipping the refactor step | Accumulating technical debt | Refactor every 3-5 green cycles | | AI implementing beyond the tests | Untested code in production | Only implement what tests require | | Not running tests after each change | Silent regressions | xcodebuild test after every edit |
testing/test-contract/ — for protocol-level test suitestesting/test-data-factory/ — for reducing test setup boilerplategenerators/test-generator/ — for standalone test generationOther measured skills in the registry, with their headline benchmark lift.