- 01-01-PLAN.md: core.py + mlb.py (executed) - 01-02-PLAN.md: nba.py + nhl.py - 01-03-PLAN.md: nfl.py + orchestrator refactor - Codebase documentation for planning context Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5.3 KiB
5.3 KiB
Testing Patterns
Analysis Date: 2026-01-09
Test Framework
Runner:
- Swift Testing (Apple's new testing framework, iOS 26+)
- Config: Built into Xcode, no separate config file
Assertion Library:
#expect()macro (289 occurrences)- Replaces XCTest's
XCTAssertEqual, etc.
Run Commands:
# Run all tests
xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test
# Run specific test suite
xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/TravelEstimatorTests test
# Run single test
xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/TestClassName/testMethodName test
Test File Organization
Location:
SportsTimeTests/*.swift- Unit testsSportsTimeUITests/*.swift- UI tests (XCTest-based)
Naming:
- Unit tests:
{Component}Tests.swift - No integration/e2e distinction in filename
Structure:
SportsTimeTests/
├── TravelEstimatorTests.swift # 50+ tests
├── SportsTimeTests.swift # DayCard tests (11+), regression tests
├── ScenarioAPlannerSwiftTests.swift # 28 tests
├── ScenarioBPlannerTests.swift # 44 tests
├── ScenarioCPlannerTests.swift # 49 tests
└── (total: 180+ unit tests)
Test Structure
Suite Organization:
import Testing
import Foundation
@testable import SportsTime
@Suite("ScenarioBPlanner Tests")
struct ScenarioBPlannerTests {
// MARK: - Test Fixtures
private func makeStadium(...) -> Stadium { ... }
private func makeGame(...) -> Game { ... }
// MARK: - Tests
@Test("handles empty game list")
func emptyGameList() {
// arrange
// act
// assert with #expect()
}
}
Patterns:
@Suite("Description")for grouping related tests@Test("Description")for individual tests (notfunc test...)#expect()for assertions- Private
make*factory functions for test fixtures
Mocking
Framework:
- No external mocking framework
- Manual test doubles via protocol conformance
Patterns:
// Factory functions create test data
private func makeGame(
id: UUID = UUID(),
stadiumId: UUID,
date: Date
) -> Game {
Game(
id: id,
homeTeamId: UUID(),
awayTeamId: UUID(),
stadiumId: stadiumId,
dateTime: date,
sport: .mlb,
season: "2026"
)
}
What to Mock:
- External services (CloudKit, network)
- Date/time (use fixed dates in tests)
What NOT to Mock:
- Pure functions (TravelEstimator calculations)
- Domain models
Fixtures and Factories
Test Data:
// Factory pattern in test structs
private func makeStadium(
id: UUID = UUID(),
name: String,
city: String,
state: String,
latitude: Double,
longitude: Double,
sport: Sport = .mlb
) -> Stadium { ... }
private func date(_ string: String) -> Date {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm"
formatter.timeZone = TimeZone(identifier: "America/Los_Angeles")
return formatter.date(from: string)!
}
private func defaultConstraints() -> DrivingConstraints { ... }
Location:
- Factory functions: Defined in each test struct under
// MARK: - Test Fixtures - No shared fixtures directory
Coverage
Requirements:
- No enforced coverage target
- Focus on critical paths (planning engine, travel estimation)
Configuration:
- Xcode built-in coverage via scheme settings
- No separate coverage tool
Test Types
Unit Tests (SportsTimeTests/):
- Test single function/component in isolation
- Pure logic tests (no network, no persistence)
- Fast: milliseconds per test
- Examples:
TravelEstimatorTests,ScenarioAPlannerTests
UI Tests (SportsTimeUITests/):
- XCTest-based (older framework)
- Test user flows end-to-end
- Slower, requires simulator
Common Patterns
Async Testing:
@Test("async operation succeeds")
func asyncOperation() async {
let result = await asyncFunction()
#expect(result == expected)
}
Error Testing:
@Test("throws on invalid input")
func invalidInput() throws {
#expect(throws: SomeError.self) {
try functionThatThrows()
}
}
Known Distance Testing:
@Test("LA to SF distance is approximately 350 miles")
func laToSfDistance() {
let distance = TravelEstimator.haversineDistanceMiles(
from: Coordinate(latitude: 34.05, longitude: -118.24),
to: Coordinate(latitude: 37.77, longitude: -122.42)
)
// Known distance is ~350 miles
#expect(distance > 340 && distance < 360)
}
Regression Test Pattern:
// Regression test for handling duplicate game IDs without crashing
@Test("deduplicates games with same ID")
func duplicateGameHandling() {
// Setup with duplicate IDs
// Verify first occurrence preserved
// Verify no crash
}
Bug Fix Protocol
From CLAUDE.md:
- Write failing test that reproduces bug
- Fix the bug
- Verify test passes along with all existing tests
- Name tests descriptively:
test_Component_Condition_Expected
Testing analysis: 2026-01-09 Update when test patterns change