5.5 KiB
5.5 KiB
Workflow: Write and Run Tests
<required_reading> Read these reference files NOW:
- references/testing-tdd.md
- references/testing-debugging.md </required_reading>
Ask the user:
- New tests for existing code?
- Tests for new feature (TDD)?
- Fix a bug with regression test?
What Claude tests (automated):
- Core logic (data transforms, calculations, algorithms)
- State management (models, relationships)
- Service layer (mocked dependencies)
- Edge cases (nil, empty, boundaries)
What user tests (manual):
- UX feel and visual polish
- Real hardware/device integration
- Performance under real conditions
Step 2: Set Up Test Target
If tests don't exist yet:
# Add test target to project.yml (XcodeGen)
targets:
AppNameTests:
type: bundle.unit-test
platform: macOS
sources:
- path: Tests
dependencies:
- target: AppName
Or create test files manually in Xcode's test target.
Step 3: Write Tests
Unit Tests (Logic)
import Testing
@testable import AppName
struct ItemTests {
@Test func itemCreation() {
let item = Item(name: "Test", value: 42)
#expect(item.name == "Test")
#expect(item.value == 42)
}
@Test func itemValidation() {
let emptyItem = Item(name: "", value: 0)
#expect(!emptyItem.isValid)
}
@Test(arguments: [0, -1, 1000001])
func invalidValues(value: Int) {
let item = Item(name: "Test", value: value)
#expect(!item.isValid)
}
}
State Tests
struct AppStateTests {
@Test func addItem() {
let state = AppState()
let item = Item(name: "New", value: 10)
state.addItem(item)
#expect(state.items.count == 1)
#expect(state.items.first?.name == "New")
}
@Test func deleteItem() {
let state = AppState()
let item = Item(name: "ToDelete", value: 1)
state.addItem(item)
state.deleteItem(item)
#expect(state.items.isEmpty)
}
}
Async Tests
struct NetworkTests {
@Test func fetchItems() async throws {
let service = MockDataService()
service.mockItems = [Item(name: "Fetched", value: 5)]
let items = try await service.fetchItems()
#expect(items.count == 1)
}
@Test func fetchHandlesError() async {
let service = MockDataService()
service.shouldFail = true
await #expect(throws: NetworkError.self) {
try await service.fetchItems()
}
}
}
Edge Cases
struct EdgeCaseTests {
@Test func emptyList() {
let state = AppState()
#expect(state.items.isEmpty)
#expect(state.selectedItem == nil)
}
@Test func nilHandling() {
let item: Item? = nil
#expect(item?.name == nil)
}
@Test func boundaryValues() {
let item = Item(name: String(repeating: "a", count: 10000), value: Int.max)
#expect(item.isValid) // or test truncation behavior
}
}
Step 4: Run Tests
# Run all tests
xcodebuild test \
-project AppName.xcodeproj \
-scheme AppName \
-resultBundlePath TestResults.xcresult
# Run specific test
xcodebuild test \
-project AppName.xcodeproj \
-scheme AppName \
-only-testing:AppNameTests/ItemTests/testItemCreation
# View results
xcrun xcresulttool get test-results summary --path TestResults.xcresult
Step 5: Coverage Report
# Generate coverage
xcodebuild test \
-project AppName.xcodeproj \
-scheme AppName \
-enableCodeCoverage YES \
-resultBundlePath TestResults.xcresult
# View coverage
xcrun xccov view --report TestResults.xcresult
# Coverage as JSON
xcrun xccov view --report --json TestResults.xcresult > coverage.json
Step 6: TDD Cycle
For new features:
- Red: Write failing test for desired behavior
- Green: Write minimum code to pass
- Refactor: Clean up while keeping tests green
- Repeat: Next behavior
<test_patterns>
Arrange-Act-Assert
@Test func pattern() {
// Arrange
let state = AppState()
let item = Item(name: "Test", value: 1)
// Act
state.addItem(item)
// Assert
#expect(state.items.contains(item))
}
Mocking Dependencies
protocol DataServiceProtocol {
func fetchItems() async throws -> [Item]
}
class MockDataService: DataServiceProtocol {
var mockItems: [Item] = []
var shouldFail = false
func fetchItems() async throws -> [Item] {
if shouldFail { throw TestError.mock }
return mockItems
}
}
Testing SwiftUI State
@Test func viewModelState() {
let state = AppState()
state.items = [Item(name: "A", value: 1), Item(name: "B", value: 2)]
state.selectedItem = state.items.first
#expect(state.selectedItem?.name == "A")
}
</test_patterns>
<what_not_to_test>
- SwiftUI view rendering (use previews + manual testing)
- Apple framework behavior
- Simple getters/setters with no logic
- Private implementation details (test via public interface) </what_not_to_test>
<coverage_targets>
| Code Type | Target Coverage |
|---|---|
| Business logic | 80-100% |
| State management | 70-90% |
| Utilities/helpers | 60-80% |
| Views | 0% (manual test) |
| Generated code | 0% |
| </coverage_targets> |