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>
5.8 KiB
5.8 KiB
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
- Read - Examine function, callers, and context to understand intent
- Document - Add
/// - Expected:comment block to source file - Test - Write tests that verify documented expectations
- Batch Run - Run all tests at the end
- Fix - Failures indicate logic bugs; fix implementation to match expectations
Documentation Standard
Every tested function gets this comment format:
/// 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:
@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.swiftwith factories - Create
MockServices.swiftwith test doubles
Phase 2: Planning Engine (~2-3 hours)
Document + test in dependency order:
TravelEstimator(no dependencies)RouteFilters(no dependencies)PlanningModels(data structures)GameDAGRouter(uses TravelEstimator)ItineraryBuilder(uses filters)ScenarioPlanner+ A/B/C/D variantsTripPlanningEngine(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
- Every public function has documented expectations in source
- Tests verify expectations, not current implementation
- Property invariants hold for all inputs
- No "tests written to pass" - tests define correct behavior independently
- 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