tdd-feature
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.
Best use case
tdd-feature is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
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.
Teams using tdd-feature should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/tdd-feature/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How tdd-feature Compares
| Feature / Agent | tdd-feature | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
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.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
Related Guides
SKILL.md Source
# TDD Feature
Build new features using the red-green-refactor cycle. Tests define the spec, AI generates the implementation, tests verify correctness.
## When This Skill Activates
Use this skill when the user:
- Wants to "TDD a new feature" or "build test-first"
- Says "I want tests before code"
- Asks for "red-green-refactor" workflow
- Wants AI to generate code that's provably correct
- Is building a new module, service, or feature from scratch
## Why TDD for New Features with AI
```
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 correct
```
The test is your **acceptance criteria in code form**. AI excels at going from failing test to passing implementation — it's a concrete, unambiguous target.
## Process
### Phase 1: Define the Feature
Before writing any code or tests, understand:
1. **What does this feature do?** (user story or requirement)
2. **What are the inputs?** (parameters, user actions, data)
3. **What are the outputs?** (return values, state changes, UI updates)
4. **What are the edge cases?** (empty, nil, error, boundary)
5. **What dependencies does it need?** (network, storage, other services)
### Phase 2: Design the API Surface
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.
### Phase 3: RED — Write Failing Tests
Write tests for each behavior. Start with the simplest case and build up.
#### Order of Tests (Simple → Complex)
1. **Construction** — can you create the object?
2. **Happy path** — does the basic operation work?
3. **State verification** — does state update correctly?
4. **Edge cases** — empty, nil, boundaries
5. **Error handling** — what fails and how?
6. **Integration** — does it work with dependencies?
#### Template: Feature Test Suite
```swift
import 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).
### Phase 4: GREEN — Implement to Pass
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."
```
#### Implementation Rules
- **One test at a time** — make the first test pass, then the second, etc.
- **Write the simplest code** that passes each test
- **Don't anticipate future tests** — only satisfy current failing tests
- **Run tests after each change**
```bash
xcodebuild test -scheme YourApp \
-only-testing "YourAppTests/FavoriteManagerTests"
```
### Phase 5: REFACTOR
With all tests green, clean up the implementation:
- Extract helper methods
- Improve naming
- Remove duplication
- Optimize performance (if tests cover perf requirements)
**Run tests after every refactor step.** If any test fails, you've changed behavior — revert.
### Phase 6: Integration
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)
}
}
```
## TDD Rhythm
```
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 test
```
**Cadence matters.** If you're spending more than 5 minutes on GREEN, the test might be too big. Break it into smaller tests.
## Test Categories by Feature Type
### ViewModel Feature
```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")
}
```
### Data Layer Feature
```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")
}
```
### Business Logic Feature
```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")
}
```
## Output Format
```markdown
## TDD Feature: [Feature Name]
### API Design
```swift
// 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
```
## Common Pitfalls
| 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 |
## References
- Kent Beck, *Test-Driven Development: By Example*
- `testing/test-contract/` — for protocol-level test suites
- `testing/test-data-factory/` — for reducing test setup boilerplate
- `generators/test-generator/` — for standalone test generationRelated Skills
feature-flags
Generate feature flag infrastructure with local defaults, remote configuration, SwiftUI integration, and debug menu. Use when adding feature flags or A/B testing to iOS/macOS apps.
swiftui-ui-patterns
Best practices and example-driven guidance for building SwiftUI views and components. Use when creating or refactoring SwiftUI UI, designing tab architecture with TabView, composing screens, or needing component-specific patterns and examples.
watchOS
watchOS development guidance including SwiftUI for Watch, Watch Connectivity, complications, and watch-specific UI patterns. Use for watchOS code review, best practices, or Watch app development.
visionos-widgets
visionOS widget patterns including mounting styles, glass/paper textures, proximity-aware layouts, and spatial widget families. Use when creating or adapting widgets for visionOS.
test-data-factory
Generate test fixture factories for your models. Builder pattern and static factories for zero-boilerplate test data. Use when tests need sample data setup.
test-contract
Generate protocol/interface test suites that any implementation must pass. Define the contract once, test every implementation. Use when designing protocols or swapping implementations.
tdd-refactor-guard
Pre-refactor safety checklist. Verifies test coverage exists before AI modifies existing code. Use before asking AI to refactor anything.
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.
snapshot-test-setup
Set up SwiftUI visual regression testing with swift-snapshot-testing. Generates snapshot test boilerplate and CI configuration. Use for UI regression prevention.
integration-test-scaffold
Generate cross-module test harness with mock servers, in-memory stores, and test configuration. Use when testing networking + persistence + business logic together.
characterization-test-generator
Generates tests that capture current behavior of existing code before refactoring. Use when you need a safety net before AI-assisted refactoring or modifying legacy code.
testing
TDD and testing skills for iOS/macOS apps. Covers characterization tests, TDD workflows, test contracts, snapshot tests, and test infrastructure. Use for test-driven development, adding tests to existing code, or building test infrastructure.