docs: add TDD test rewrite design
Specification-first + property-based TDD methodology to surface logic bugs by testing expected behavior, not current implementation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
170
docs/plans/2026-01-16-tdd-test-rewrite-design.md
Normal file
170
docs/plans/2026-01-16-tdd-test-rewrite-design.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# TDD Test Rewrite Design
|
||||
|
||||
## Goal
|
||||
|
||||
Delete all existing tests and rewrite using true TDD methodology (specification-first + property-based) to surface logic bugs in the implementation. Document expected behavior for every tested function.
|
||||
|
||||
## Methodology
|
||||
|
||||
### Specification-First + Property-Based TDD
|
||||
|
||||
- **Specification-First**: Define expected behavior from requirements/intent, not current implementation
|
||||
- **Property-Based**: Define invariants that must always hold regardless of input
|
||||
- **Document-First**: Add input/output comments to source functions before writing tests
|
||||
|
||||
### The Process
|
||||
|
||||
1. **Read** - Examine function, callers, and context to understand intent
|
||||
2. **Document** - Add `/// - Expected:` comment block to source file
|
||||
3. **Test** - Write tests that verify documented expectations
|
||||
4. **Batch Run** - Run all tests at the end
|
||||
5. **Fix** - Failures indicate logic bugs; fix implementation to match expectations
|
||||
|
||||
## Documentation Standard
|
||||
|
||||
Every tested function gets this comment format:
|
||||
|
||||
```swift
|
||||
/// Calculates optimal routes between game locations.
|
||||
///
|
||||
/// - Parameter games: List of games to route between (must be chronologically sorted)
|
||||
/// - Parameter maxDailyMiles: Maximum driving distance per day (default: 500)
|
||||
/// - Returns: Array of valid routes, each containing sequential games
|
||||
///
|
||||
/// - Expected Behavior:
|
||||
/// - Empty input → returns empty array
|
||||
/// - Single game → returns array with one single-game route
|
||||
/// - Games on same day in distant cities → excludes impossible transitions
|
||||
/// - Routes respect maxDailyMiles constraint
|
||||
/// - Routes are chronologically ordered
|
||||
///
|
||||
/// - Invariants:
|
||||
/// - All returned routes have games in chronological order
|
||||
/// - No route exceeds maxDailyMiles between consecutive same-day games
|
||||
func findRoutes(games: [Game], maxDailyMiles: Int = 500) -> [[Game]]
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
Each test file has three sections:
|
||||
|
||||
```swift
|
||||
@Suite("ComponentName")
|
||||
struct ComponentNameTests {
|
||||
|
||||
// MARK: - Specification Tests
|
||||
// Tests derived from "Expected Behavior" comments
|
||||
|
||||
// MARK: - Property Tests
|
||||
// Tests that verify invariants for any input
|
||||
|
||||
// MARK: - Edge Case Tests
|
||||
// Boundary conditions and unusual inputs
|
||||
}
|
||||
```
|
||||
|
||||
### Property-Based Patterns
|
||||
|
||||
| Pattern | Example | Applies To |
|
||||
|---------|---------|------------|
|
||||
| Idempotency | `filter(filter(x)) == filter(x)` | RouteFilters |
|
||||
| Roundtrip | `decode(encode(x)) == x` | All Codable models |
|
||||
| Ordering | Routes always chronological | GameDAGRouter |
|
||||
| Bounds | Distance ≤ maxDailyMiles | TravelEstimator |
|
||||
| Conservation | Total games in = total games out | ItineraryBuilder |
|
||||
|
||||
## Execution Phases
|
||||
|
||||
### Phase 1: Delete & Infrastructure (~15 min)
|
||||
- Delete all 53 existing test files
|
||||
- Create `TestFixtures.swift` with factories
|
||||
- Create `MockServices.swift` with test doubles
|
||||
|
||||
### Phase 2: Planning Engine (~2-3 hours)
|
||||
Document + test in dependency order:
|
||||
1. `TravelEstimator` (no dependencies)
|
||||
2. `RouteFilters` (no dependencies)
|
||||
3. `PlanningModels` (data structures)
|
||||
4. `GameDAGRouter` (uses TravelEstimator)
|
||||
5. `ItineraryBuilder` (uses filters)
|
||||
6. `ScenarioPlanner` + A/B/C/D variants
|
||||
7. `TripPlanningEngine` (orchestrator)
|
||||
|
||||
### Phase 3: Domain Models (~1.5-2 hours)
|
||||
15 files: Game, Trip, Stadium, Team, Sport, Region, Division, TripStop, TravelSegment, TripPreferences, Progress, DynamicSport, TripPoll, AnySport, AchievementDefinitions
|
||||
|
||||
Focus: Codable roundtrips, computed properties, classification logic
|
||||
|
||||
### Phase 4: Services (~2-3 hours)
|
||||
23 files covering:
|
||||
- Error enums (CloudKitError, SyncError, etc.)
|
||||
- Result types (SyncResult, ScoreResolutionResult)
|
||||
- Configuration states (unconfigured, invalid)
|
||||
- Rate limiting, caching, network states
|
||||
|
||||
### Phase 5: Export (~30 min)
|
||||
2 files: ShareableContent types, POISearchService types
|
||||
|
||||
### Phase 6: Run & Fix (~1-2 hours)
|
||||
- Run full test suite
|
||||
- Failures = logic bugs in implementation
|
||||
- Fix source code to match documented expectations
|
||||
- If expectation was wrong, discuss and adjust
|
||||
|
||||
## Bug Handling
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| Implementation bug | Fix source code to match documented expectation |
|
||||
| Expectation wrong | Discuss, update documentation, adjust test |
|
||||
|
||||
Failures are treated as **bugs to fix**, not tests to adjust.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Every public function has documented expectations in source
|
||||
2. Tests verify expectations, not current implementation
|
||||
3. Property invariants hold for all inputs
|
||||
4. No "tests written to pass" - tests define correct behavior independently
|
||||
5. All tests pass after fixing surfaced bugs
|
||||
|
||||
## Files to Test
|
||||
|
||||
### Planning (11 files)
|
||||
- TripPlanningEngine.swift
|
||||
- GameDAGRouter.swift
|
||||
- ItineraryBuilder.swift
|
||||
- RouteFilters.swift
|
||||
- TravelEstimator.swift
|
||||
- ScenarioPlanner.swift
|
||||
- ScenarioAPlanner.swift
|
||||
- ScenarioBPlanner.swift
|
||||
- ScenarioCPlanner.swift
|
||||
- ScenarioDPlanner.swift
|
||||
- PlanningModels.swift
|
||||
|
||||
### Domain (15 files)
|
||||
- Game.swift, Stadium.swift, Team.swift, Trip.swift
|
||||
- TripStop.swift, TravelSegment.swift, TripPreferences.swift
|
||||
- Sport.swift, Region.swift, Division.swift
|
||||
- Progress.swift, DynamicSport.swift, TripPoll.swift
|
||||
- AnySport.swift, AchievementDefinitions.swift
|
||||
|
||||
### Services (23 files)
|
||||
- CloudKitService.swift, DataProvider.swift, BootstrapService.swift
|
||||
- CanonicalSyncService.swift, BackgroundSyncManager.swift
|
||||
- LocationService.swift, LocationPermissionManager.swift
|
||||
- AchievementEngine.swift, EVChargingService.swift
|
||||
- GameMatcher.swift, StadiumProximityMatcher.swift
|
||||
- FreeScoreAPI.swift, HistoricalGameScraper.swift
|
||||
- NetworkMonitor.swift, RateLimiter.swift
|
||||
- PollService.swift, DeepLinkHandler.swift
|
||||
- RouteDescriptionGenerator.swift, SuggestedTripsGenerator.swift
|
||||
- SyncCancellationToken.swift, VisitPhotoService.swift
|
||||
- PhotoMetadataExtractor.swift, StadiumIdentityService.swift
|
||||
|
||||
### Export (2 files)
|
||||
- ShareableContent.swift
|
||||
- POISearchService.swift
|
||||
|
||||
**Total: ~52 source files, estimated 8-10 hours**
|
||||
Reference in New Issue
Block a user