- 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>
224 lines
5.3 KiB
Markdown
224 lines
5.3 KiB
Markdown
# 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:**
|
|
```bash
|
|
# 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 tests
|
|
- `SportsTimeUITests/*.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:**
|
|
```swift
|
|
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 (not `func test...`)
|
|
- `#expect()` for assertions
|
|
- Private `make*` factory functions for test fixtures
|
|
|
|
## Mocking
|
|
|
|
**Framework:**
|
|
- No external mocking framework
|
|
- Manual test doubles via protocol conformance
|
|
|
|
**Patterns:**
|
|
```swift
|
|
// 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:**
|
|
```swift
|
|
// 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:**
|
|
```swift
|
|
@Test("async operation succeeds")
|
|
func asyncOperation() async {
|
|
let result = await asyncFunction()
|
|
#expect(result == expected)
|
|
}
|
|
```
|
|
|
|
**Error Testing:**
|
|
```swift
|
|
@Test("throws on invalid input")
|
|
func invalidInput() throws {
|
|
#expect(throws: SomeError.self) {
|
|
try functionThatThrows()
|
|
}
|
|
}
|
|
```
|
|
|
|
**Known Distance Testing:**
|
|
```swift
|
|
@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:**
|
|
```swift
|
|
// 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`:
|
|
1. Write failing test that reproduces bug
|
|
2. Fix the bug
|
|
3. Verify test passes along with all existing tests
|
|
4. Name tests descriptively: `test_Component_Condition_Expected`
|
|
|
|
---
|
|
|
|
*Testing analysis: 2026-01-09*
|
|
*Update when test patterns change*
|