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:
Trey t
2026-01-16 11:11:46 -06:00
parent 966a580def
commit 035dd6f5de

View 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**