Test Specification Skill
Generate comprehensive test specification and QA plan for iOS/macOS app testing.
Metadata
- Name: test-spec
- Version: 1.0.0
- Role: QA Engineer
- Author: ProductAgent Team
When This Skill Activates
This skill activates when the user says:
- "generate test spec"
- "create QA plan"
- "write testing guide"
- "generate test cases"
- "create test specification"
Description
You are a QA Engineer AI agent specializing in iOS/macOS app testing. Your job is to transform product requirements and implementation details into a comprehensive test specification that ensures quality, identifies edge cases, and provides clear test cases for both automated and manual testing.
Prerequisites
Before activating this skill, ensure:
- PRD exists (from prd-generator skill) with features and acceptance criteria
- IMPLEMENTATION_GUIDE exists (from implementation-guide skill) with code structure
- UX_SPEC exists (from ux-spec skill) for UI testing scenarios
Input Sources
Read and extract information from:
docs/PRD.md
- All features with acceptance criteria
- User stories (Given/When/Then format)
- Success criteria
- Non-functional requirements
docs/IMPLEMENTATION_GUIDE.md
- All ViewModels to test
- All data models to test
- API endpoints to test
- File structure for organizing tests
docs/UX_SPEC.md
- All user flows
- All interactions
- All states (empty, loading, error)
- Edge cases documented
docs/ARCHITECTURE.md
- Testing strategy overview
- Tech stack (for choosing testing tools)
Output
Generate: docs/TEST_SPEC.md
Structure:
# Test Specification: [App Name]
**Version**: 1.0.0
**Last Updated**: [Date]
**Status**: Draft / In Review / Approved
**QA Engineer**: QA Engineer AI
**Platform**: iOS [Version]+
---
## 1. Test Strategy
### 1.1 Test Pyramid
Our testing approach follows the test pyramid:
/\\
/ \\ UI Tests (10%)
/ \\ Critical user journeys, happy paths
/------\\
/ \\ Integration Tests (20%)
/ \\ API integration, data persistence, service layer
/------------\
/ \ Unit Tests (70%)
/ \ ViewModels, Models, Utilities, Business Logic
/------------------\
**Rationale**:
- **Unit Tests (70%)**: Fast, reliable, easy to maintain. Focus on business logic.
- **Integration Tests (20%)**: Test component interactions (API + Database, ViewModel + Service).
- **UI Tests (10%)**: Slow and brittle, only for critical user flows.
### 1.2 Testing Levels
**Level 1: Unit Testing**
- **Scope**: Individual functions, methods, ViewModels, Models
- **Tools**: XCTest
- **Run Frequency**: On every commit (CI/CD)
- **Target Coverage**: 80%+ code coverage
**Level 2: Integration Testing**
- **Scope**: Multiple components working together
- **Tools**: XCTest with mock/stub services
- **Run Frequency**: On every PR merge
- **Target Coverage**: All critical data flows
**Level 3: UI Testing**
- **Scope**: End-to-end user journeys
- **Tools**: XCUITest
- **Run Frequency**: Before release
- **Target Coverage**: All P0 user flows
**Level 4: Manual Testing**
- **Scope**: Exploratory testing, edge cases, UX validation
- **Tools**: TestFlight beta
- **Run Frequency**: Before each release
- **Target Coverage**: Full app walkthrough
### 1.3 Test Environments
**Development**:
- Local Xcode testing
- In-memory database (SwiftData)
- Mock API responses
- Fast feedback loop
**Staging**:
- TestFlight internal testing
- Staging API environment
- Real backend integration
- Pre-production validation
**Production**:
- Phased rollout (10% → 50% → 100%)
- Real user monitoring
- Crash analytics
- Performance monitoring
### 1.4 Testing Tools
| Tool | Purpose | When to Use |
|------|---------|-------------|
| XCTest | Unit & integration tests | Always |
| XCUITest | UI automation tests | Critical flows |
| TestFlight | Beta testing | Pre-release |
| Xcode Instruments | Performance profiling | Optimization phase |
| Accessibility Inspector | Accessibility audit | Every release |
| Network Link Conditioner | Network testing | Edge case testing |
---
## 2. Unit Test Cases
### 2.1 Data Model Tests
Test all `@Model` classes from ARCHITECTURE.md.
#### Test Suite: User Model
**File**: `[AppName]Tests/ModelTests/UserTests.swift`
| Test Case | Setup | Input | Expected Output | Priority |
|-----------|-------|-------|-----------------|----------|
| testUserInitialization | None | name: "John Doe", email: "john@test.com" | User object created with UUID, timestamps set | P0 |
| testUserInitializationWithEmptyName | None | name: "", email: "test@test.com" | User created but isValid returns false | P1 |
| testEmailValidation_Valid | User instance | email: "valid@example.com" | isValid returns true | P0 |
| testEmailValidation_Invalid | User instance | email: "invalid.com" | isValid returns false | P0 |
| testEmailValidation_Empty | User instance | email: "" | isValid returns false | P0 |
| testDisplayName_SingleName | User with name: "John" | Call displayName | Returns "John" | P1 |
| testDisplayName_FullName | User with name: "John Doe" | Call displayName | Returns "John" | P1 |
| testInitials_SingleName | User with name: "John" | Call initials | Returns "J" | P2 |
| testInitials_FullName | User with name: "John Doe" | Call initials | Returns "JD" | P2 |
| testUpdateProfile_Name | User instance | updateProfile(name: "Jane") | name updated, updatedAt changed | P1 |
| testUpdateProfile_Email | User instance | updateProfile(email: "new@test.com") | email updated, updatedAt changed | P1 |
| testCodable_Encoding | User instance | Encode to JSON | Valid JSON with snake_case keys | P1 |
| testCodable_Decoding | JSON data | Decode from JSON | User object created correctly | P1 |
**Implementation Example**:
```swift
import XCTest
@testable import [AppName]
final class UserTests: XCTestCase {
var sut: User! // System Under Test
override func setUp() {
super.setUp()
// Setup runs before each test
sut = User(name: "Test User", email: "test@example.com")
}
override func tearDown() {
// Cleanup runs after each test
sut = nil
super.tearDown()
}
// Test: User initialization creates valid object
func testUserInitialization() {
// Given: Setup in setUp()
// When: User is initialized (done in setUp)
// Then: Verify properties
XCTAssertNotNil(sut.id, "ID should be generated")
XCTAssertEqual(sut.name, "Test User")
XCTAssertEqual(sut.email, "test@example.com")
XCTAssertNotNil(sut.createdAt)
XCTAssertNotNil(sut.updatedAt)
XCTAssertTrue(sut.items.isEmpty, "New user should have no items")
}
// Test: Valid email passes validation
func testEmailValidation_Valid() {
// Given
sut.email = "valid@example.com"
// When
let isValid = sut.isValid
// Then
XCTAssertTrue(isValid, "Valid email should pass validation")
}
// Test: Invalid email fails validation
func testEmailValidation_Invalid() {
// Given
sut.email = "invalid.com" // Missing @
// When
let isValid = sut.isValid
// Then
XCTAssertFalse(isValid, "Invalid email should fail validation")
}
// Test: Display name returns first name only
func testDisplayName_FullName() {
// Given
sut.name = "John Doe"
// When
let displayName = sut.displayName
// Then
XCTAssertEqual(displayName, "John", "Display name should be first name only")
}
// Test: Initials are correctly generated
func testInitials_FullName() {
// Given
sut.name = "John Doe"
// When
let initials = sut.initials
// Then
XCTAssertEqual(initials, "JD", "Initials should be JD")
}
// Test: Update profile changes name and timestamp
func testUpdateProfile_Name() {
// Given
let originalUpdatedAt = sut.updatedAt
sleep(1) // Ensure timestamp changes
// When
sut.updateProfile(name: "Jane Doe")
// Then
XCTAssertEqual(sut.name, "Jane Doe")
XCTAssertNotEqual(sut.updatedAt, originalUpdatedAt, "updatedAt should change")
}
// Test: Codable encoding produces valid JSON
func testCodable_Encoding() throws {
// Given: User from setUp
// When
let encoder = JSONEncoder()
let data = try encoder.encode(sut)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
// Then
XCTAssertNotNil(json)
XCTAssertEqual(json?["name"] as? String, "Test User")
XCTAssertEqual(json?["email"] as? String, "test@example.com")
XCTAssertNotNil(json?["id"])
}
}
Repeat for all models: Item, Category, etc.
Test Suite: Item Model
[Similar structure for ItemTests.swift]
Key Test Cases:
- Initialization
- Validation (title not empty, description not empty)
- Status updates
- Relationships (owner, category)
- Codable conformance
2.2 ViewModel Tests
Test all ViewModels from IMPLEMENTATION_GUIDE.
Test Suite: HomeViewModel
File: [AppName]Tests/ViewModelTests/HomeViewModelTests.swift
| Test Case |
Mock Setup |
Action |
Expected Result |
Priority |
| testInitialState |
New ViewModel |
None |
items empty, isLoading false, showError false |
P0 |
| testLoadItems_Success |
Mock API returns 3 items |
Call loadItems() |
isLoading becomes false, items count = 3, no error |
P0 |
| testLoadItems_Failure |
Mock API throws error |
Call loadItems() |
isLoading false, showError true, errorMessage set |
P0 |
| testLoadItems_EmptyResponse |
Mock API returns empty array |
Call loadItems() |
items empty, no error |
P1 |
| testRefresh |
3 items already loaded |
Call refresh() |
isLoading true then false, items refreshed |
P1 |
| testDeleteItem_Success |
3 items loaded |
Delete item[1] |
items count = 2, deleted item removed |
P0 |
| testDeleteItem_Failure |
Mock API throws error on delete |
Delete item |
showError true, item not removed |
P1 |
| testToggleItemStatus_Active |
Item with status .active |
toggleItemStatus(item) |
item.status becomes .completed |
P0 |
| testToggleItemStatus_Completed |
Item with status .completed |
toggleItemStatus(item) |
item.status becomes .active |
P0 |
| testSearchItems_MatchFound |
3 items, searchQuery = "Test" |
Set searchQuery |
items filtered to matching only |
P1 |
| testSearchItems_NoMatch |
3 items, searchQuery = "XYZ" |
Set searchQuery |
items empty |
P1 |
| testSearchItems_EmptyQuery |
searchQuery was "Test" |
Set searchQuery = "" |
All items shown again |
P1 |
| testLoadItems_LocalFirst |
Mock delays API response |
Call loadItems() |
Local items shown immediately, then synced |
P2 |
Implementation Example:
import XCTest
@testable import [AppName]
final class HomeViewModelTests: XCTestCase {
var sut: HomeViewModel!
var mockAPIClient: MockAPIClient!
var mockDataManager: MockDataManager!
override func setUp() {
super.setUp()
mockAPIClient = MockAPIClient()
mockDataManager = MockDataManager()
sut = HomeViewModel(
apiClient: mockAPIClient,
dataManager: mockDataManager
)
}
override func tearDown() {
sut = nil
mockAPIClient = nil
mockDataManager = nil
super.tearDown()
}
// Test: Initial state is correct
@MainActor
func testInitialState() {
// Then
XCTAssertTrue(sut.items.isEmpty)
XCTAssertFalse(sut.isLoading)
XCTAssertFalse(sut.showError)
XCTAssertTrue(sut.searchQuery.isEmpty)
}
// Test: Loading items succeeds
@MainActor
func testLoadItems_Success() async {
// Given
let mockItems = [
Item(title: "Item 1", description: "Desc 1", owner: mockUser()),
Item(title: "Item 2", description: "Desc 2", owner: mockUser()),
Item(title: "Item 3", description: "Desc 3", owner: mockUser())
]
mockDataManager.itemsToReturn = mockItems
mockAPIClient.itemsToReturn = mockItems
// When
await sut.loadItems()
// Then
XCTAssertEqual(sut.items.count, 3)
XCTAssertFalse(sut.isLoading)
XCTAssertFalse(sut.showError)
}
// Test: Loading items handles error
@MainActor
func testLoadItems_Failure() async {
// Given
mockDataManager.shouldThrowError = true
// When
await sut.loadItems()
// Then
XCTAssertTrue(sut.showError)
XCTAssertFalse(sut.errorMessage.isEmpty)
XCTAssertFalse(sut.isLoading)
}
// Test: Deleting item succeeds
@MainActor
func testDeleteItem_Success() async {
// Given
let mockItems = [
Item(title: "Item 1", description: "Desc 1", owner: mockUser()),
Item(title: "Item 2", description: "Desc 2", owner: mockUser())
]
mockDataManager.itemsToReturn = mockItems
await sut.loadItems()
let itemToDelete = sut.items[0]
// When
await sut.deleteItem(itemToDelete)
// Then
XCTAssertEqual(sut.items.count, 1)
XCTAssertFalse(sut.items.contains(where: { $0.id == itemToDelete.id }))
}
// Test: Toggle item status changes status
@MainActor
func testToggleItemStatus_ActiveToCompleted() async {
// Given
let item = Item(title: "Test", description: "Desc", owner: mockUser())
item.status = .active
mockDataManager.itemsToReturn = [item]
await sut.loadItems()
// When
await sut.toggleItemStatus(item)
// Then
XCTAssertEqual(item.status, .completed)
}
// Helper: Create mock user
private func mockUser() -> User {
return User(name: "Mock User", email: "mock@test.com")
}
}
// MARK: - Mock APIClient
class MockAPIClient: APIClient {
var itemsToReturn: [Item] = []
var shouldThrowError = false
override func get<T: Decodable>(_ endpoint: String) async throws -> T {
if shouldThrowError {
throw APIError.serverError(500)
}
return itemsToReturn as! T
}
override func delete<T: Decodable>(_ endpoint: String) async throws -> T {
if shouldThrowError {
throw APIError.serverError(500)
}
return EmptyResponse() as! T
}
}
// MARK: - Mock DataManager
class MockDataManager: DataManager {
var itemsToReturn: [Item] = []
var shouldThrowError = false
override func fetch<T: PersistentModel>(
_ type: T.Type,
predicate: Predicate<T>? = nil,
sortBy: [SortDescriptor<T>] = []
) throws -> [T] {
if shouldThrowError {
throw DataManagerError.fetchFailed(NSError(domain: "test", code: 1))
}
return itemsToReturn as! [T]
}
override func delete<T: PersistentModel>(_ model: T) throws {
if shouldThrowError {
throw DataManagerError.deleteFailed(NSError(domain: "test", code: 1))
}
// Remove from mock array
itemsToReturn.removeAll { ($0 as AnyObject) === (model as AnyObject) }
}
}
Repeat for all ViewModels: ItemDetailViewModel, AddEditItemViewModel, SettingsViewModel, etc.
2.3 Service Tests
Test API client, DataManager, and other services.
Test Suite: APIClient
Key Test Cases:
- Successful GET request
- Successful POST request with body
- 401 Unauthorized error handling
- 404 Not Found error handling
- 500 Server error handling
- Network timeout handling
- JSON decoding error handling
- Invalid URL handling
Test Suite: DataManager
Key Test Cases:
- Create model instance
- Fetch all instances
- Fetch with predicate filter
- Fetch by ID
- Update model instance
- Delete model instance
- Delete all instances
- Specialized queries (fetchItems(byStatus:), searchItems(query:))
3. Integration Test Cases
Test multiple components working together.
3.1 Data Persistence Integration
| Test Case |
Setup |
Action |
Expected Result |
Priority |
| testSaveAndFetch |
Empty database |
Create user, save, fetch all |
User retrieved successfully |
P0 |
| testUpdateAndFetch |
User in database |
Modify user, save, fetch |
Changes persisted |
P0 |
| testDeleteAndFetch |
User in database |
Delete user, fetch all |
User no longer exists |
P0 |
| testRelationshipCascade |
User with 3 items |
Delete user |
All items also deleted |
P0 |
| testQueryWithPredicate |
5 items (2 active, 3 completed) |
Fetch active only |
Returns 2 items |
P1 |
3.2 API Integration
| Test Case |
Setup |
Action |
Expected Result |
Priority |
| testFetchItems_APIToDatabase |
Empty database, mock API returns 3 items |
Call fetchItems() |
3 items saved to database |
P0 |
| testCreateItem_DatabaseToAPI |
Mock API |
Create item locally, sync |
POST request sent to API |
P0 |
| testSyncConflict |
Local and remote modified same item |
Sync |
Conflict resolved (last write wins) |
P1 |
| testOfflineMode |
No network |
Create item locally |
Item saved locally, queued for sync |
P0 |
| testOnlineRestore |
Previously offline |
Network restored |
Queued items synced to API |
P1 |
4. UI Test Cases
Test end-to-end user journeys with XCUITest.
4.1 Critical User Journey 1: Onboarding and First Item Creation
Priority: P0 (Must pass before release)
Estimated Duration: 30 seconds
Frequency: Run on every release candidate
| Step |
User Action |
Expected UI State |
Assertions |
Priority |
| 1 |
Launch app (first time) |
Welcome screen shown |
"Welcome to [AppName]" exists, "Get Started" button exists |
P0 |
| 2 |
Tap "Get Started" button |
Registration screen shown |
Email and name fields visible, "Continue" button exists |
P0 |
| 3 |
Enter name: "Test User" |
Name field populated |
Name field value = "Test User" |
P0 |
| 4 |
Enter email: "test@example.com" |
Email field populated |
Email field value = "test@example.com" |
P0 |
| 5 |
Tap "Continue" button |
Onboarding Step 1 shown |
Progress dots visible, skip button visible, next button exists |
P0 |
| 6 |
Swipe left (or tap Next) |
Onboarding Step 2 shown |
Different content shown, progress indicator updated |
P1 |
| 7 |
Tap "Skip" button |
Home screen shown with empty state |
"No Items Yet" message visible, "+" button in navbar |
P0 |
| 8 |
Tap "+" button in navbar |
Add Item sheet presented |
Title field, description field, save button visible |
P0 |
| 9 |
Enter title: "My First Item" |
Title populated |
Title field value = "My First Item" |
P0 |
| 10 |
Enter description: "Test description" |
Description populated |
Description field value = "Test description" |
P0 |
| 11 |
Tap "Save" button |
Sheet dismissed, Home screen shown |
Item appears in list with title "My First Item" |
P0 |
| 12 |
Verify item visible |
Item card visible in list |
Card contains "My First Item" and "Test description" |
P0 |
Implementation:
import XCTest
final class OnboardingUITests: XCTestCase {
let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
// Reset app to first-time user state
app.launchArguments = ["--uitesting", "--reset"]
app.launch()
}
func testOnboardingAndFirstItemCreation() throws {
// Step 1: Verify welcome screen
let welcomeTitle = app.staticTexts["Welcome to [AppName]"]
XCTAssertTrue(welcomeTitle.waitForExistence(timeout: 2))
let getStartedButton = app.buttons["Get Started"]
XCTAssertTrue(getStartedButton.exists)
// Step 2: Tap Get Started
getStartedButton.tap()
// Step 3-4: Fill registration
let nameField = app.textFields["Name"]
XCTAssertTrue(nameField.waitForExistence(timeout: 2))
nameField.tap()
nameField.typeText("Test User")
let emailField = app.textFields["Email"]
emailField.tap()
emailField.typeText("test@example.com")
// Step 5: Continue to onboarding
app.buttons["Continue"].tap()
// Verify onboarding screen
XCTAssertTrue(app.staticTexts["Onboarding Step 1"].waitForExistence(timeout: 2))
// Step 7: Skip onboarding
app.buttons["Skip"].tap()
// Step 8: Verify Home empty state
let emptyStateMessage = app.staticTexts["No Items Yet"]
XCTAssertTrue(emptyStateMessage.waitForExistence(timeout: 2))
// Step 9: Tap + button
let addButton = app.navigationBars.buttons["Add Item"]
XCTAssertTrue(addButton.exists)
addButton.tap()
// Step 10-11: Fill add item form
let titleField = app.textFields["Title"]
XCTAssertTrue(titleField.waitForExistence(timeout: 2))
titleField.tap()
titleField.typeText("My First Item")
let descriptionField = app.textViews["Description"]
descriptionField.tap()
descriptionField.typeText("Test description")
// Step 12: Save
app.buttons["Save"].tap()
// Step 13: Verify item appears
let itemCard = app.staticTexts["My First Item"]
XCTAssertTrue(itemCard.waitForExistence(timeout: 3))
// Verify subtitle
let subtitle = app.staticTexts["Test description"]
XCTAssertTrue(subtitle.exists)
}
}
4.2 Critical User Journey 2: View and Edit Item
[Similar detailed test case with steps, assertions, and implementation]
4.3 Critical User Journey 3: Delete Item
[Similar structure]
4.4 Critical User Journey 4: Search Items
[Similar structure]
5. Accessibility Testing
5.1 VoiceOver Testing
Manual Test Cases:
| Screen |
Element |
Expected VoiceOver Label |
Expected Behavior |
Priority |
| Home |
Add button |
"Add new item, button" |
Announces label, tapping activates button |
P0 |
| Home |
Item card |
"[Item title], [subtitle], [status], button" |
Announces all relevant info, tapping navigates |
P0 |
| Home |
Search field |
"Search items, search field" |
Announces hint, typing works |
P1 |
| Detail |
Edit button |
"Edit item, button" |
Clear action announced |
P0 |
| Add/Edit |
Title field |
"Title, text field, required" |
Indicates required field |
P0 |
| Add/Edit |
Save button |
"Save item, button" |
Clear action, disabled state announced |
P0 |
| Settings |
Toggle |
"Notifications, on, toggle switch" |
State announced, double-tap toggles |
P1 |
Automated VoiceOver Tests:
func testVoiceOverLabels() {
// Enable accessibility features
app.launchArguments += ["-UIAccessibilitySpeakScreenEnabled", "1"]
app.launch()
// Navigate to Home
let addButton = app.buttons["Add Item"]
XCTAssertEqual(addButton.label, "Add new item")
XCTAssertTrue(addButton.isAccessibilityElement)
// Check item card
let itemCard = app.buttons.matching(identifier: "ItemCard").firstMatch
XCTAssertTrue(itemCard.label.contains("Item title"))
XCTAssertTrue(itemCard.isAccessibilityElement)
}
5.2 Dynamic Type Testing
Test Cases:
| Size |
Expected Behavior |
Pass Criteria |
Priority |
| Extra Small (XS) |
All text readable, buttons not truncated |
No overlap, all elements visible |
P1 |
| Default |
Standard layout |
Reference design |
P0 |
| Extra Large (XL) |
Text larger, layout adjusts |
Scrollable, no truncation |
P0 |
| Accessibility XXL |
Text very large, stacked layout |
Fully readable, scrollable |
P1 |
Test Implementation:
func testDynamicType_ExtraLarge() {
// Set large text size
app.launchArguments += ["-UIPreferredContentSizeCategoryName", "UICTContentSizeCategoryAccessibilityXL"]
app.launch()
// Verify text is not truncated
let title = app.staticTexts["Item Title"]
XCTAssertTrue(title.exists)
XCTAssertFalse(title.label.hasSuffix("..."), "Text should not be truncated")
// Verify buttons are still tappable
let addButton = app.buttons["Add Item"]
XCTAssertTrue(addButton.exists)
XCTAssertGreaterThan(addButton.frame.height, 44, "Button should be at least 44pt tall")
}
5.3 Color Contrast Testing
Automated Checks:
| Element |
Foreground |
Background |
Ratio |
WCAG AA Pass |
Priority |
| Primary button text |
#FFFFFF |
#007AFF |
4.5:1+ |
✅ Yes |
P0 |
| Body text |
#000000 |
#FFFFFF |
21:1 |
✅ Yes |
P0 |
| Secondary text |
#3C3C43 (60%) |
#FFFFFF |
4.6:1 |
✅ Yes |
P0 |
| Link text |
#007AFF |
#FFFFFF |
4.5:1+ |
✅ Yes |
P0 |
Tool: Xcode Accessibility Inspector → Color Contrast
6. Performance Testing
6.1 Performance Benchmarks
| Metric |
Target |
Measurement Method |
Priority |
| App launch time (cold) |
< 1.5s |
XCTest measure block |
P0 |
| App launch time (warm) |
< 0.5s |
XCTest measure block |
P1 |
| Home screen load |
< 500ms |
Time to first content |
P0 |
| Scroll FPS |
60 fps |
Instruments (Core Animation) |
P0 |
| Memory usage (idle) |
< 50MB |
Instruments (Allocations) |
P1 |
| Memory usage (active) |
< 150MB |
Instruments (Allocations) |
P1 |
| Network request time |
< 2s (95th percentile) |
Analytics |
P1 |
Test Implementation:
func testLaunchPerformance() {
measure(metrics: [XCTApplicationLaunchMetric()]) {
app.launch()
}
// Xcode will report average launch time
}
func testHomeScreenLoadPerformance() {
app.launch()
measure {
// Navigate to Home
app.tabBars.buttons["Home"].tap()
// Wait for content
_ = app.tables.cells.firstMatch.waitForExistence(timeout: 5)
}
}
6.2 Load Testing
| Scenario |
Data Volume |
Expected Behavior |
Pass Criteria |
Priority |
| Large list |
1000 items |
Smooth scrolling, no lag |
60 fps maintained |
P1 |
| Heavy images |
50 high-res images |
Progressive loading |
No memory warnings |
P1 |
| Large search |
Search in 1000 items |
Results in < 1s |
Fast filtering |
P2 |
| Rapid scrolling |
Scroll 100 items quickly |
No crashes, smooth |
No dropped frames |
P1 |
7. Edge Cases & Error Scenarios
7.1 Network Conditions
| Condition |
User Action |
Expected Behavior |
Verification |
Priority |
| No network (airplane mode) |
Launch app |
Cached data shown, offline banner |
Data visible, banner "No internet connection" |
P0 |
| No network |
Tap refresh |
Error message, cached data remains |
"Can't refresh. Check connection." |
P0 |
| Slow network (3G) |
Load items |
Loading indicator, timeout after 30s |
Spinner shows, then timeout message |
P1 |
| Network timeout |
API call exceeds 30s |
Timeout error, graceful fallback |
"Request timed out. Try again." |
P1 |
| Network restored |
Was offline, now online |
Auto-sync, banner dismissed |
Data refreshes, "Back online" toast |
P1 |
| Intermittent connection |
Random disconnects |
Retry with exponential backoff |
Eventually succeeds or clear error |
P2 |
Test with: Settings → Developer → Network Link Conditioner
7.2 Data Validation
| Invalid Input |
User Action |
Expected Behavior |
Verification |
Priority |
| Empty required field |
Tap Save with empty title |
Error message, save disabled |
Red outline, "Title is required" |
P0 |
| Invalid email format |
Enter "invalid.com" |
Inline error shown |
"Please enter a valid email" |
P0 |
| Too long input |
Enter 500 character title |
Character limit enforced |
Max 150 chars, counter shows "150/150" |
P1 |
| Special characters |
Enter "Item " in title |
Sanitized or rejected |
No script execution, saved safely |
P0 |
| Duplicate entry |
Create item with existing title |
Warning shown, allow override |
"Item exists. Continue?" |
P1 |
| Invalid date |
Select date in past (if not allowed) |
Error shown, date reset |
"Date must be in future" |
P1 |
7.3 Permission Handling
| Permission |
Denied State |
User Action |
Expected Behavior |
Priority |
| Camera |
Denied |
Tap "Add Photo" |
Alert: "Camera access needed. Open Settings?" |
P0 |
| Camera |
Denied |
User opens Settings |
Deep link to app settings |
P1 |
| Photos |
Denied |
Tap "Choose Photo" |
Photo picker not shown, request permission |
P0 |
| Notifications |
Denied |
App needs to notify |
App works without notifications |
P1 |
| Location |
Denied |
Feature requires location |
Feature disabled, explanation shown |
P1 |
7.4 Device Conditions
| Condition |
Expected Behavior |
Verification |
Priority |
| Low battery mode |
Reduced animations, background tasks paused |
Respects system setting |
P1 |
| Low storage |
Can't save images |
Error: "Not enough storage" |
P1 |
| Rotation (if supported) |
Layout adapts |
No clipping, all elements visible |
P1 |
| Multitasking (iPad) |
App works in split view |
Responsive layout |
P2 |
| Background mode |
App suspended, returns |
State preserved |
P0 |
| App killed by system |
Force quit, relaunch |
State restored from last save |
P0 |
8. Security Testing
8.1 Data Security
| Test |
Action |
Expected Result |
Priority |
| Keychain storage |
Store auth token |
Token encrypted in Keychain |
P0 |
| API token exposure |
Inspect network traffic |
Token in Authorization header only |
P0 |
| Sensitive data logging |
Check console logs |
No passwords/tokens logged |
P0 |
| Screenshot protection |
App contains sensitive data |
Blur/hide when app switching |
P1 |
8.2 Privacy
| Test |
Action |
Expected Result |
Priority |
| Privacy manifest |
Check PrivacyInfo.xcprivacy |
All data collection disclosed |
P0 |
| Third-party SDKs |
Inspect network requests |
No unexpected tracking |
P0 |
| Data export |
User requests data |
All data provided in readable format |
P1 |
| Account deletion |
User deletes account |
All data removed from server |
P0 |
9. Regression Testing
Run before every release to ensure new changes don't break existing functionality.
9.1 Smoke Test Checklist
Duration: 15 minutes
Frequency: Every build
9.2 Full Regression Suite
Duration: 2 hours
Frequency: Every release candidate
10. Beta Testing (TestFlight)
10.1 Beta Test Plan
Participants: 20-50 external beta testers
Duration: 2 weeks before App Store submission
Platforms: iPhone (all sizes), iPad (if supported)
iOS Versions: [minimum deployment target] – latest
Recruitment:
- Internal team members
- Trusted users from target audience
- Mix of tech-savvy and average users
- Geographic diversity (if localized)
Feedback Collection:
- In-app feedback form: Button in Settings → "Send Feedback"
- TestFlight feedback: Native TestFlight feedback
- Crash reports: Automatically collected
- Survey: Email survey after 1 week of use
10.2 Beta Testing Focus Areas
Week 1: Functionality Testing
Week 2: Real-World Usage
10.3 Beta Success Criteria
Before proceeding to App Store submission:
Feedback Analysis:
- Categorize feedback: bug, feature request, UX issue, question
- Prioritize: P0 (fix before launch), P1 (fix in v1.1), P2 (backlog)
- Track resolution: open, in progress, fixed, won't fix
11. Test Automation
11.1 CI/CD Integration
Platform: Xcode Cloud / GitHub Actions / Fastlane
Trigger: On every pull request and merge to main
Pipeline:
name: iOS CI
on: [pull_request, push]
jobs:
test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Select Xcode version
run: sudo xcode-select -s /Applications/Xcode_15.0.app
- name: Run SwiftLint
run: swiftlint lint --strict
- name: Build
run: |
xcodebuild clean build \
-scheme [AppName] \
-destination 'platform=iOS Simulator,name=iPhone 15'
- name: Run Unit Tests
run: |
xcodebuild test \
-scheme [AppName] \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-only-testing:[AppName]Tests
- name: Run UI Tests
run: |
xcodebuild test \
-scheme [AppName] \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-only-testing:[AppName]UITests
- name: Generate Code Coverage
run: |
xcodebuild test \
-scheme [AppName] \
-enableCodeCoverage YES \
-derivedDataPath ./DerivedData
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v3
11.2 Test Reports
Generate HTML test report:
xcodebuild test \
-scheme [AppName] \
-resultBundlePath TestResults.xcresult
xcrun xcresulttool get --format html TestResults.xcresult > test-report.html
Code Coverage Report:
- Target: 80%+ overall coverage
- Critical paths: 95%+ coverage (authentication, data persistence)
- View in Xcode: Product → Show Code Coverage
12. Bug Tracking
12.1 Bug Report Template
File in issue tracker (GitHub Issues, Jira, etc.):
**Title**: [Concise, descriptive bug title]
**Severity**: Critical / High / Medium / Low
**Priority**: P0 / P1 / P2 / P3
**Affected Version**: 1.0.0
**Environment**:
- Device: iPhone 15 Pro
- iOS Version: 17.5
- App Version: 1.0.0 (Build 42)
**Steps to Reproduce**:
1. Launch app
2. Navigate to Home
3. Tap on item "Test Item"
4. Observe crash
**Expected Behavior**:
Item detail screen should open without crash.
**Actual Behavior**:
App crashes immediately when tapping item.
**Screenshots/Videos**:
[Attach if available]
**Crash Logs**:
[Paste crash log from Xcode Organizer]
**Additional Context**:
Happens only with items that have nil thumbnailURL.
**Possible Fix**:
Add nil check in ItemDetailView line 47.
12.2 Bug Severity Definitions
Critical (P0):
- App crashes on launch
- Data loss
- Security vulnerability
- Cannot complete core user flow
- Fix immediately, hotfix if needed
High (P1):
- Feature completely broken
- Frequent crashes (> 1% of sessions)
- Major UI issues
- Fix before next release
Medium (P2):
- Feature partially broken
- Rare crashes (< 0.1% of sessions)
- Minor UI issues
- Workaround exists
- Fix in next minor update
Low (P3):
- Cosmetic issues
- Feature enhancement
- Edge case issues
- Fix when convenient
13. Post-Launch Monitoring
13.1 Metrics to Track
Crash Analytics (Xcode Organizer, Firebase Crashlytics):
- Crash-free rate: Target > 99.5%
- Top crashes by volume
- Crash trends over time
Performance Monitoring:
- App launch time: p50, p95, p99
- Screen load time
- API response time
- Memory usage
Usage Analytics (App Analytics, Firebase Analytics):
- Daily/Monthly Active Users (DAU/MAU)
- Session length
- Session frequency
- Feature usage rates
- Funnel completion rates
App Store Metrics:
- Rating: Target > 4.5 stars
- Reviews: Respond to all reviews
- Download trends
- Conversion rate (impressions → downloads)
User Feedback:
- Support ticket volume
- Common complaints
- Feature requests
- Positive feedback themes
13.2 Alerting Thresholds
Critical Alerts (Page immediately):
- Crash rate > 1%
- App Store rating drops below 3.0
- API error rate > 5%
- Server downtime
High Priority Alerts (Check within 1 hour):
- Crash rate > 0.5%
- App Store rating drops below 4.0
- API error rate > 2%
- Support ticket spike
Medium Priority (Check daily):
- Crash rate > 0.1%
- Feature usage < 10% of users (for key features)
- Session length decreasing trend
14. Test Documentation
14.1 Test Case Format
Each test case should document:
- ID: TC-001
- Feature: Feature name from PRD
- Priority: P0/P1/P2
- Type: Unit/Integration/UI/Manual
- Preconditions: State before test
- Steps: Numbered steps
- Expected Result: Clear pass criteria
- Actual Result: Filled after execution
- Status: Pass/Fail/Blocked
- Notes: Any observations
14.2 Test Execution Tracking
Test Run Spreadsheet:
| ID |
Test Case |
Priority |
Type |
Status |
Notes |
Date |
Tester |
| TC-001 |
User login success |
P0 |
UI |
Pass |
|
2024-01-15 |
QA |
| TC-002 |
User login invalid email |
P0 |
UI |
Pass |
|
2024-01-15 |
QA |
| TC-003 |
Create item success |
P0 |
UI |
Fail |
Crash on save |
2024-01-15 |
QA |
Test Coverage Matrix:
| Feature (PRD) |
Unit Tests |
Integration Tests |
UI Tests |
Manual Tests |
Coverage % |
| User registration |
12/12 ✅ |
3/3 ✅ |
2/2 ✅ |
5/5 ✅ |
100% |
| Item CRUD |
20/20 ✅ |
5/5 ✅ |
8/10 ⚠️ |
10/10 ✅ |
90% |
| Search |
8/8 ✅ |
2/2 ✅ |
3/3 ✅ |
3/3 ✅ |
100% |
15. Release Readiness Checklist
Before submitting to App Store:
15.1 Testing Complete
15.2 Manual Testing
15.3 Beta Testing
…(truncated)
1---
2name: test-spec
3description: Generates comprehensive test specification with unit tests, UI tests, accessibility testing, and beta testing plan. Creates TEST_SPEC.md from PRD and implementation specs. Use when creating QA strategy.
4allowed-tools: [Read, Write, Glob, Grep, AskUserQuestion]
5last_verified: 2026-07-16
6review_by: 2027-06-22
7os_version: iOS 27 / macOS 27
8---
9
10# Test Specification Skill
11
12Generate comprehensive test specification and QA plan for iOS/macOS app testing.
13
14## Metadata
15- **Name**: test-spec
16- **Version**: 1.0.0
17- **Role**: QA Engineer
18- **Author**: ProductAgent Team
19
20## When This Skill Activates
21
22This skill activates when the user says:
23- "generate test spec"
24- "create QA plan"
25- "write testing guide"
26- "generate test cases"
27- "create test specification"
28
29## Description
30
31You are a QA Engineer AI agent specializing in iOS/macOS app testing. Your job is to transform product requirements and implementation details into a comprehensive test specification that ensures quality, identifies edge cases, and provides clear test cases for both automated and manual testing.
32
33## Prerequisites
34
35Before activating this skill, ensure:
361. PRD exists (from prd-generator skill) with features and acceptance criteria
372. IMPLEMENTATION_GUIDE exists (from implementation-guide skill) with code structure
383. UX_SPEC exists (from ux-spec skill) for UI testing scenarios
39
40## Input Sources
41
42Read and extract information from:
43
441. **docs/PRD.md**
45 - All features with acceptance criteria
46 - User stories (Given/When/Then format)
47 - Success criteria
48 - Non-functional requirements
49
502. **docs/IMPLEMENTATION_GUIDE.md**
51 - All ViewModels to test
52 - All data models to test
53 - API endpoints to test
54 - File structure for organizing tests
55
563. **docs/UX_SPEC.md**
57 - All user flows
58 - All interactions
59 - All states (empty, loading, error)
60 - Edge cases documented
61
624. **docs/ARCHITECTURE.md**
63 - Testing strategy overview
64 - Tech stack (for choosing testing tools)
65
66## Output
67
68Generate: **docs/TEST_SPEC.md**
69
70Structure:
71
72```markdown
73# Test Specification: [App Name]
74
75**Version**: 1.0.0
76**Last Updated**: [Date]
77**Status**: Draft / In Review / Approved
78**QA Engineer**: QA Engineer AI
79**Platform**: iOS [Version]+
80
81---
82
83## 1. Test Strategy
84
85### 1.1 Test Pyramid
86
87Our testing approach follows the test pyramid:
88
89```
90 /\\
91 / \\ UI Tests (10%)
92 / \\ Critical user journeys, happy paths
93 /------\\
94 / \\ Integration Tests (20%)
95 / \\ API integration, data persistence, service layer
96 /------------\\
97 / \\ Unit Tests (70%)
98 / \\ ViewModels, Models, Utilities, Business Logic
99/------------------\\
100```
101
102**Rationale**:
103- **Unit Tests (70%)**: Fast, reliable, easy to maintain. Focus on business logic.
104- **Integration Tests (20%)**: Test component interactions (API + Database, ViewModel + Service).
105- **UI Tests (10%)**: Slow and brittle, only for critical user flows.
106
107### 1.2 Testing Levels
108
109**Level 1: Unit Testing**
110- **Scope**: Individual functions, methods, ViewModels, Models
111- **Tools**: XCTest
112- **Run Frequency**: On every commit (CI/CD)
113- **Target Coverage**: 80%+ code coverage
114
115**Level 2: Integration Testing**
116- **Scope**: Multiple components working together
117- **Tools**: XCTest with mock/stub services
118- **Run Frequency**: On every PR merge
119- **Target Coverage**: All critical data flows
120
121**Level 3: UI Testing**
122- **Scope**: End-to-end user journeys
123- **Tools**: XCUITest
124- **Run Frequency**: Before release
125- **Target Coverage**: All P0 user flows
126
127**Level 4: Manual Testing**
128- **Scope**: Exploratory testing, edge cases, UX validation
129- **Tools**: TestFlight beta
130- **Run Frequency**: Before each release
131- **Target Coverage**: Full app walkthrough
132
133### 1.3 Test Environments
134
135**Development**:
136- Local Xcode testing
137- In-memory database (SwiftData)
138- Mock API responses
139- Fast feedback loop
140
141**Staging**:
142- TestFlight internal testing
143- Staging API environment
144- Real backend integration
145- Pre-production validation
146
147**Production**:
148- Phased rollout (10% → 50% → 100%)
149- Real user monitoring
150- Crash analytics
151- Performance monitoring
152
153### 1.4 Testing Tools
154
155| Tool | Purpose | When to Use |
156|------|---------|-------------|
157| XCTest | Unit & integration tests | Always |
158| XCUITest | UI automation tests | Critical flows |
159| TestFlight | Beta testing | Pre-release |
160| Xcode Instruments | Performance profiling | Optimization phase |
161| Accessibility Inspector | Accessibility audit | Every release |
162| Network Link Conditioner | Network testing | Edge case testing |
163
164---
165
166## 2. Unit Test Cases
167
168### 2.1 Data Model Tests
169
170Test all `@Model` classes from ARCHITECTURE.md.
171
172#### Test Suite: User Model
173
174**File**: `[AppName]Tests/ModelTests/UserTests.swift`
175
176| Test Case | Setup | Input | Expected Output | Priority |
177|-----------|-------|-------|-----------------|----------|
178| testUserInitialization | None | name: "John Doe", email: "john@test.com" | User object created with UUID, timestamps set | P0 |
179| testUserInitializationWithEmptyName | None | name: "", email: "test@test.com" | User created but isValid returns false | P1 |
180| testEmailValidation_Valid | User instance | email: "valid@example.com" | isValid returns true | P0 |
181| testEmailValidation_Invalid | User instance | email: "invalid.com" | isValid returns false | P0 |
182| testEmailValidation_Empty | User instance | email: "" | isValid returns false | P0 |
183| testDisplayName_SingleName | User with name: "John" | Call displayName | Returns "John" | P1 |
184| testDisplayName_FullName | User with name: "John Doe" | Call displayName | Returns "John" | P1 |
185| testInitials_SingleName | User with name: "John" | Call initials | Returns "J" | P2 |
186| testInitials_FullName | User with name: "John Doe" | Call initials | Returns "JD" | P2 |
187| testUpdateProfile_Name | User instance | updateProfile(name: "Jane") | name updated, updatedAt changed | P1 |
188| testUpdateProfile_Email | User instance | updateProfile(email: "new@test.com") | email updated, updatedAt changed | P1 |
189| testCodable_Encoding | User instance | Encode to JSON | Valid JSON with snake_case keys | P1 |
190| testCodable_Decoding | JSON data | Decode from JSON | User object created correctly | P1 |
191
192**Implementation Example**:
193
194```swift
195import XCTest
196@testable import [AppName]
197
198final class UserTests: XCTestCase {
199 var sut: User! // System Under Test
200
201 override func setUp() {
202 super.setUp()
203 // Setup runs before each test
204 sut = User(name: "Test User", email: "test@example.com")
205 }
206
207 override func tearDown() {
208 // Cleanup runs after each test
209 sut = nil
210 super.tearDown()
211 }
212
213 // Test: User initialization creates valid object
214 func testUserInitialization() {
215 // Given: Setup in setUp()
216
217 // When: User is initialized (done in setUp)
218
219 // Then: Verify properties
220 XCTAssertNotNil(sut.id, "ID should be generated")
221 XCTAssertEqual(sut.name, "Test User")
222 XCTAssertEqual(sut.email, "test@example.com")
223 XCTAssertNotNil(sut.createdAt)
224 XCTAssertNotNil(sut.updatedAt)
225 XCTAssertTrue(sut.items.isEmpty, "New user should have no items")
226 }
227
228 // Test: Valid email passes validation
229 func testEmailValidation_Valid() {
230 // Given
231 sut.email = "valid@example.com"
232
233 // When
234 let isValid = sut.isValid
235
236 // Then
237 XCTAssertTrue(isValid, "Valid email should pass validation")
238 }
239
240 // Test: Invalid email fails validation
241 func testEmailValidation_Invalid() {
242 // Given
243 sut.email = "invalid.com" // Missing @
244
245 // When
246 let isValid = sut.isValid
247
248 // Then
249 XCTAssertFalse(isValid, "Invalid email should fail validation")
250 }
251
252 // Test: Display name returns first name only
253 func testDisplayName_FullName() {
254 // Given
255 sut.name = "John Doe"
256
257 // When
258 let displayName = sut.displayName
259
260 // Then
261 XCTAssertEqual(displayName, "John", "Display name should be first name only")
262 }
263
264 // Test: Initials are correctly generated
265 func testInitials_FullName() {
266 // Given
267 sut.name = "John Doe"
268
269 // When
270 let initials = sut.initials
271
272 // Then
273 XCTAssertEqual(initials, "JD", "Initials should be JD")
274 }
275
276 // Test: Update profile changes name and timestamp
277 func testUpdateProfile_Name() {
278 // Given
279 let originalUpdatedAt = sut.updatedAt
280 sleep(1) // Ensure timestamp changes
281
282 // When
283 sut.updateProfile(name: "Jane Doe")
284
285 // Then
286 XCTAssertEqual(sut.name, "Jane Doe")
287 XCTAssertNotEqual(sut.updatedAt, originalUpdatedAt, "updatedAt should change")
288 }
289
290 // Test: Codable encoding produces valid JSON
291 func testCodable_Encoding() throws {
292 // Given: User from setUp
293
294 // When
295 let encoder = JSONEncoder()
296 let data = try encoder.encode(sut)
297 let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
298
299 // Then
300 XCTAssertNotNil(json)
301 XCTAssertEqual(json?["name"] as? String, "Test User")
302 XCTAssertEqual(json?["email"] as? String, "test@example.com")
303 XCTAssertNotNil(json?["id"])
304 }
305}
306```
307
308**Repeat for all models**: Item, Category, etc.
309
310---
311
312#### Test Suite: Item Model
313
314[Similar structure for ItemTests.swift]
315
316**Key Test Cases**:
317- Initialization
318- Validation (title not empty, description not empty)
319- Status updates
320- Relationships (owner, category)
321- Codable conformance
322
323---
324
325### 2.2 ViewModel Tests
326
327Test all ViewModels from IMPLEMENTATION_GUIDE.
328
329#### Test Suite: HomeViewModel
330
331**File**: `[AppName]Tests/ViewModelTests/HomeViewModelTests.swift`
332
333| Test Case | Mock Setup | Action | Expected Result | Priority |
334|-----------|------------|--------|-----------------|----------|
335| testInitialState | New ViewModel | None | items empty, isLoading false, showError false | P0 |
336| testLoadItems_Success | Mock API returns 3 items | Call loadItems() | isLoading becomes false, items count = 3, no error | P0 |
337| testLoadItems_Failure | Mock API throws error | Call loadItems() | isLoading false, showError true, errorMessage set | P0 |
338| testLoadItems_EmptyResponse | Mock API returns empty array | Call loadItems() | items empty, no error | P1 |
339| testRefresh | 3 items already loaded | Call refresh() | isLoading true then false, items refreshed | P1 |
340| testDeleteItem_Success | 3 items loaded | Delete item[1] | items count = 2, deleted item removed | P0 |
341| testDeleteItem_Failure | Mock API throws error on delete | Delete item | showError true, item not removed | P1 |
342| testToggleItemStatus_Active | Item with status .active | toggleItemStatus(item) | item.status becomes .completed | P0 |
343| testToggleItemStatus_Completed | Item with status .completed | toggleItemStatus(item) | item.status becomes .active | P0 |
344| testSearchItems_MatchFound | 3 items, searchQuery = "Test" | Set searchQuery | items filtered to matching only | P1 |
345| testSearchItems_NoMatch | 3 items, searchQuery = "XYZ" | Set searchQuery | items empty | P1 |
346| testSearchItems_EmptyQuery | searchQuery was "Test" | Set searchQuery = "" | All items shown again | P1 |
347| testLoadItems_LocalFirst | Mock delays API response | Call loadItems() | Local items shown immediately, then synced | P2 |
348
349**Implementation Example**:
350
351```swift
352import XCTest
353@testable import [AppName]
354
355final class HomeViewModelTests: XCTestCase {
356 var sut: HomeViewModel!
357 var mockAPIClient: MockAPIClient!
358 var mockDataManager: MockDataManager!
359
360 override func setUp() {
361 super.setUp()
362 mockAPIClient = MockAPIClient()
363 mockDataManager = MockDataManager()
364 sut = HomeViewModel(
365 apiClient: mockAPIClient,
366 dataManager: mockDataManager
367 )
368 }
369
370 override func tearDown() {
371 sut = nil
372 mockAPIClient = nil
373 mockDataManager = nil
374 super.tearDown()
375 }
376
377 // Test: Initial state is correct
378 @MainActor
379 func testInitialState() {
380 // Then
381 XCTAssertTrue(sut.items.isEmpty)
382 XCTAssertFalse(sut.isLoading)
383 XCTAssertFalse(sut.showError)
384 XCTAssertTrue(sut.searchQuery.isEmpty)
385 }
386
387 // Test: Loading items succeeds
388 @MainActor
389 func testLoadItems_Success() async {
390 // Given
391 let mockItems = [
392 Item(title: "Item 1", description: "Desc 1", owner: mockUser()),
393 Item(title: "Item 2", description: "Desc 2", owner: mockUser()),
394 Item(title: "Item 3", description: "Desc 3", owner: mockUser())
395 ]
396 mockDataManager.itemsToReturn = mockItems
397 mockAPIClient.itemsToReturn = mockItems
398
399 // When
400 await sut.loadItems()
401
402 // Then
403 XCTAssertEqual(sut.items.count, 3)
404 XCTAssertFalse(sut.isLoading)
405 XCTAssertFalse(sut.showError)
406 }
407
408 // Test: Loading items handles error
409 @MainActor
410 func testLoadItems_Failure() async {
411 // Given
412 mockDataManager.shouldThrowError = true
413
414 // When
415 await sut.loadItems()
416
417 // Then
418 XCTAssertTrue(sut.showError)
419 XCTAssertFalse(sut.errorMessage.isEmpty)
420 XCTAssertFalse(sut.isLoading)
421 }
422
423 // Test: Deleting item succeeds
424 @MainActor
425 func testDeleteItem_Success() async {
426 // Given
427 let mockItems = [
428 Item(title: "Item 1", description: "Desc 1", owner: mockUser()),
429 Item(title: "Item 2", description: "Desc 2", owner: mockUser())
430 ]
431 mockDataManager.itemsToReturn = mockItems
432 await sut.loadItems()
433 let itemToDelete = sut.items[0]
434
435 // When
436 await sut.deleteItem(itemToDelete)
437
438 // Then
439 XCTAssertEqual(sut.items.count, 1)
440 XCTAssertFalse(sut.items.contains(where: { $0.id == itemToDelete.id }))
441 }
442
443 // Test: Toggle item status changes status
444 @MainActor
445 func testToggleItemStatus_ActiveToCompleted() async {
446 // Given
447 let item = Item(title: "Test", description: "Desc", owner: mockUser())
448 item.status = .active
449 mockDataManager.itemsToReturn = [item]
450 await sut.loadItems()
451
452 // When
453 await sut.toggleItemStatus(item)
454
455 // Then
456 XCTAssertEqual(item.status, .completed)
457 }
458
459 // Helper: Create mock user
460 private func mockUser() -> User {
461 return User(name: "Mock User", email: "mock@test.com")
462 }
463}
464
465// MARK: - Mock APIClient
466
467class MockAPIClient: APIClient {
468 var itemsToReturn: [Item] = []
469 var shouldThrowError = false
470
471 override func get<T: Decodable>(_ endpoint: String) async throws -> T {
472 if shouldThrowError {
473 throw APIError.serverError(500)
474 }
475 return itemsToReturn as! T
476 }
477
478 override func delete<T: Decodable>(_ endpoint: String) async throws -> T {
479 if shouldThrowError {
480 throw APIError.serverError(500)
481 }
482 return EmptyResponse() as! T
483 }
484}
485
486// MARK: - Mock DataManager
487
488class MockDataManager: DataManager {
489 var itemsToReturn: [Item] = []
490 var shouldThrowError = false
491
492 override func fetch<T: PersistentModel>(
493 _ type: T.Type,
494 predicate: Predicate<T>? = nil,
495 sortBy: [SortDescriptor<T>] = []
496 ) throws -> [T] {
497 if shouldThrowError {
498 throw DataManagerError.fetchFailed(NSError(domain: "test", code: 1))
499 }
500 return itemsToReturn as! [T]
501 }
502
503 override func delete<T: PersistentModel>(_ model: T) throws {
504 if shouldThrowError {
505 throw DataManagerError.deleteFailed(NSError(domain: "test", code: 1))
506 }
507 // Remove from mock array
508 itemsToReturn.removeAll { ($0 as AnyObject) === (model as AnyObject) }
509 }
510}
511```
512
513**Repeat for all ViewModels**: ItemDetailViewModel, AddEditItemViewModel, SettingsViewModel, etc.
514
515---
516
517### 2.3 Service Tests
518
519Test API client, DataManager, and other services.
520
521#### Test Suite: APIClient
522
523**Key Test Cases**:
524- Successful GET request
525- Successful POST request with body
526- 401 Unauthorized error handling
527- 404 Not Found error handling
528- 500 Server error handling
529- Network timeout handling
530- JSON decoding error handling
531- Invalid URL handling
532
533#### Test Suite: DataManager
534
535**Key Test Cases**:
536- Create model instance
537- Fetch all instances
538- Fetch with predicate filter
539- Fetch by ID
540- Update model instance
541- Delete model instance
542- Delete all instances
543- Specialized queries (fetchItems(byStatus:), searchItems(query:))
544
545---
546
547## 3. Integration Test Cases
548
549Test multiple components working together.
550
551### 3.1 Data Persistence Integration
552
553| Test Case | Setup | Action | Expected Result | Priority |
554|-----------|-------|--------|-----------------|----------|
555| testSaveAndFetch | Empty database | Create user, save, fetch all | User retrieved successfully | P0 |
556| testUpdateAndFetch | User in database | Modify user, save, fetch | Changes persisted | P0 |
557| testDeleteAndFetch | User in database | Delete user, fetch all | User no longer exists | P0 |
558| testRelationshipCascade | User with 3 items | Delete user | All items also deleted | P0 |
559| testQueryWithPredicate | 5 items (2 active, 3 completed) | Fetch active only | Returns 2 items | P1 |
560
561### 3.2 API Integration
562
563| Test Case | Setup | Action | Expected Result | Priority |
564|-----------|-------|--------|-----------------|----------|
565| testFetchItems_APIToDatabase | Empty database, mock API returns 3 items | Call fetchItems() | 3 items saved to database | P0 |
566| testCreateItem_DatabaseToAPI | Mock API | Create item locally, sync | POST request sent to API | P0 |
567| testSyncConflict | Local and remote modified same item | Sync | Conflict resolved (last write wins) | P1 |
568| testOfflineMode | No network | Create item locally | Item saved locally, queued for sync | P0 |
569| testOnlineRestore | Previously offline | Network restored | Queued items synced to API | P1 |
570
571---
572
573## 4. UI Test Cases
574
575Test end-to-end user journeys with XCUITest.
576
577### 4.1 Critical User Journey 1: Onboarding and First Item Creation
578
579**Priority**: P0 (Must pass before release)
580**Estimated Duration**: 30 seconds
581**Frequency**: Run on every release candidate
582
583| Step | User Action | Expected UI State | Assertions | Priority |
584|------|-------------|-------------------|------------|----------|
585| 1 | Launch app (first time) | Welcome screen shown | "Welcome to [AppName]" exists, "Get Started" button exists | P0 |
586| 2 | Tap "Get Started" button | Registration screen shown | Email and name fields visible, "Continue" button exists | P0 |
587| 3 | Enter name: "Test User" | Name field populated | Name field value = "Test User" | P0 |
588| 4 | Enter email: "test@example.com" | Email field populated | Email field value = "test@example.com" | P0 |
589| 5 | Tap "Continue" button | Onboarding Step 1 shown | Progress dots visible, skip button visible, next button exists | P0 |
590| 6 | Swipe left (or tap Next) | Onboarding Step 2 shown | Different content shown, progress indicator updated | P1 |
591| 7 | Tap "Skip" button | Home screen shown with empty state | "No Items Yet" message visible, "+" button in navbar | P0 |
592| 8 | Tap "+" button in navbar | Add Item sheet presented | Title field, description field, save button visible | P0 |
593| 9 | Enter title: "My First Item" | Title populated | Title field value = "My First Item" | P0 |
594| 10 | Enter description: "Test description" | Description populated | Description field value = "Test description" | P0 |
595| 11 | Tap "Save" button | Sheet dismissed, Home screen shown | Item appears in list with title "My First Item" | P0 |
596| 12 | Verify item visible | Item card visible in list | Card contains "My First Item" and "Test description" | P0 |
597
598**Implementation**:
599
600```swift
601import XCTest
602
603final class OnboardingUITests: XCTestCase {
604 let app = XCUIApplication()
605
606 override func setUpWithError() throws {
607 continueAfterFailure = false
608
609 // Reset app to first-time user state
610 app.launchArguments = ["--uitesting", "--reset"]
611 app.launch()
612 }
613
614 func testOnboardingAndFirstItemCreation() throws {
615 // Step 1: Verify welcome screen
616 let welcomeTitle = app.staticTexts["Welcome to [AppName]"]
617 XCTAssertTrue(welcomeTitle.waitForExistence(timeout: 2))
618
619 let getStartedButton = app.buttons["Get Started"]
620 XCTAssertTrue(getStartedButton.exists)
621
622 // Step 2: Tap Get Started
623 getStartedButton.tap()
624
625 // Step 3-4: Fill registration
626 let nameField = app.textFields["Name"]
627 XCTAssertTrue(nameField.waitForExistence(timeout: 2))
628 nameField.tap()
629 nameField.typeText("Test User")
630
631 let emailField = app.textFields["Email"]
632 emailField.tap()
633 emailField.typeText("test@example.com")
634
635 // Step 5: Continue to onboarding
636 app.buttons["Continue"].tap()
637
638 // Verify onboarding screen
639 XCTAssertTrue(app.staticTexts["Onboarding Step 1"].waitForExistence(timeout: 2))
640
641 // Step 7: Skip onboarding
642 app.buttons["Skip"].tap()
643
644 // Step 8: Verify Home empty state
645 let emptyStateMessage = app.staticTexts["No Items Yet"]
646 XCTAssertTrue(emptyStateMessage.waitForExistence(timeout: 2))
647
648 // Step 9: Tap + button
649 let addButton = app.navigationBars.buttons["Add Item"]
650 XCTAssertTrue(addButton.exists)
651 addButton.tap()
652
653 // Step 10-11: Fill add item form
654 let titleField = app.textFields["Title"]
655 XCTAssertTrue(titleField.waitForExistence(timeout: 2))
656 titleField.tap()
657 titleField.typeText("My First Item")
658
659 let descriptionField = app.textViews["Description"]
660 descriptionField.tap()
661 descriptionField.typeText("Test description")
662
663 // Step 12: Save
664 app.buttons["Save"].tap()
665
666 // Step 13: Verify item appears
667 let itemCard = app.staticTexts["My First Item"]
668 XCTAssertTrue(itemCard.waitForExistence(timeout: 3))
669
670 // Verify subtitle
671 let subtitle = app.staticTexts["Test description"]
672 XCTAssertTrue(subtitle.exists)
673 }
674}
675```
676
677---
678
679### 4.2 Critical User Journey 2: View and Edit Item
680
681[Similar detailed test case with steps, assertions, and implementation]
682
683### 4.3 Critical User Journey 3: Delete Item
684
685[Similar structure]
686
687### 4.4 Critical User Journey 4: Search Items
688
689[Similar structure]
690
691---
692
693## 5. Accessibility Testing
694
695### 5.1 VoiceOver Testing
696
697**Manual Test Cases**:
698
699| Screen | Element | Expected VoiceOver Label | Expected Behavior | Priority |
700|--------|---------|-------------------------|-------------------|----------|
701| Home | Add button | "Add new item, button" | Announces label, tapping activates button | P0 |
702| Home | Item card | "[Item title], [subtitle], [status], button" | Announces all relevant info, tapping navigates | P0 |
703| Home | Search field | "Search items, search field" | Announces hint, typing works | P1 |
704| Detail | Edit button | "Edit item, button" | Clear action announced | P0 |
705| Add/Edit | Title field | "Title, text field, required" | Indicates required field | P0 |
706| Add/Edit | Save button | "Save item, button" | Clear action, disabled state announced | P0 |
707| Settings | Toggle | "Notifications, on, toggle switch" | State announced, double-tap toggles | P1 |
708
709**Automated VoiceOver Tests**:
710
711```swift
712func testVoiceOverLabels() {
713 // Enable accessibility features
714 app.launchArguments += ["-UIAccessibilitySpeakScreenEnabled", "1"]
715 app.launch()
716
717 // Navigate to Home
718 let addButton = app.buttons["Add Item"]
719 XCTAssertEqual(addButton.label, "Add new item")
720 XCTAssertTrue(addButton.isAccessibilityElement)
721
722 // Check item card
723 let itemCard = app.buttons.matching(identifier: "ItemCard").firstMatch
724 XCTAssertTrue(itemCard.label.contains("Item title"))
725 XCTAssertTrue(itemCard.isAccessibilityElement)
726}
727```
728
729### 5.2 Dynamic Type Testing
730
731**Test Cases**:
732
733| Size | Expected Behavior | Pass Criteria | Priority |
734|------|-------------------|---------------|----------|
735| Extra Small (XS) | All text readable, buttons not truncated | No overlap, all elements visible | P1 |
736| Default | Standard layout | Reference design | P0 |
737| Extra Large (XL) | Text larger, layout adjusts | Scrollable, no truncation | P0 |
738| Accessibility XXL | Text very large, stacked layout | Fully readable, scrollable | P1 |
739
740**Test Implementation**:
741
742```swift
743func testDynamicType_ExtraLarge() {
744 // Set large text size
745 app.launchArguments += ["-UIPreferredContentSizeCategoryName", "UICTContentSizeCategoryAccessibilityXL"]
746 app.launch()
747
748 // Verify text is not truncated
749 let title = app.staticTexts["Item Title"]
750 XCTAssertTrue(title.exists)
751 XCTAssertFalse(title.label.hasSuffix("..."), "Text should not be truncated")
752
753 // Verify buttons are still tappable
754 let addButton = app.buttons["Add Item"]
755 XCTAssertTrue(addButton.exists)
756 XCTAssertGreaterThan(addButton.frame.height, 44, "Button should be at least 44pt tall")
757}
758```
759
760### 5.3 Color Contrast Testing
761
762**Automated Checks**:
763
764| Element | Foreground | Background | Ratio | WCAG AA Pass | Priority |
765|---------|------------|------------|-------|--------------|----------|
766| Primary button text | #FFFFFF | #007AFF | 4.5:1+ | ✅ Yes | P0 |
767| Body text | #000000 | #FFFFFF | 21:1 | ✅ Yes | P0 |
768| Secondary text | #3C3C43 (60%) | #FFFFFF | 4.6:1 | ✅ Yes | P0 |
769| Link text | #007AFF | #FFFFFF | 4.5:1+ | ✅ Yes | P0 |
770
771**Tool**: Xcode Accessibility Inspector → Color Contrast
772
773---
774
775## 6. Performance Testing
776
777### 6.1 Performance Benchmarks
778
779| Metric | Target | Measurement Method | Priority |
780|--------|--------|-------------------|----------|
781| App launch time (cold) | < 1.5s | XCTest measure block | P0 |
782| App launch time (warm) | < 0.5s | XCTest measure block | P1 |
783| Home screen load | < 500ms | Time to first content | P0 |
784| Scroll FPS | 60 fps | Instruments (Core Animation) | P0 |
785| Memory usage (idle) | < 50MB | Instruments (Allocations) | P1 |
786| Memory usage (active) | < 150MB | Instruments (Allocations) | P1 |
787| Network request time | < 2s (95th percentile) | Analytics | P1 |
788
789**Test Implementation**:
790
791```swift
792func testLaunchPerformance() {
793 measure(metrics: [XCTApplicationLaunchMetric()]) {
794 app.launch()
795 }
796 // Xcode will report average launch time
797}
798
799func testHomeScreenLoadPerformance() {
800 app.launch()
801
802 measure {
803 // Navigate to Home
804 app.tabBars.buttons["Home"].tap()
805
806 // Wait for content
807 _ = app.tables.cells.firstMatch.waitForExistence(timeout: 5)
808 }
809}
810```
811
812### 6.2 Load Testing
813
814| Scenario | Data Volume | Expected Behavior | Pass Criteria | Priority |
815|----------|-------------|-------------------|---------------|----------|
816| Large list | 1000 items | Smooth scrolling, no lag | 60 fps maintained | P1 |
817| Heavy images | 50 high-res images | Progressive loading | No memory warnings | P1 |
818| Large search | Search in 1000 items | Results in < 1s | Fast filtering | P2 |
819| Rapid scrolling | Scroll 100 items quickly | No crashes, smooth | No dropped frames | P1 |
820
821---
822
823## 7. Edge Cases & Error Scenarios
824
825### 7.1 Network Conditions
826
827| Condition | User Action | Expected Behavior | Verification | Priority |
828|-----------|-------------|-------------------|--------------|----------|
829| No network (airplane mode) | Launch app | Cached data shown, offline banner | Data visible, banner "No internet connection" | P0 |
830| No network | Tap refresh | Error message, cached data remains | "Can't refresh. Check connection." | P0 |
831| Slow network (3G) | Load items | Loading indicator, timeout after 30s | Spinner shows, then timeout message | P1 |
832| Network timeout | API call exceeds 30s | Timeout error, graceful fallback | "Request timed out. Try again." | P1 |
833| Network restored | Was offline, now online | Auto-sync, banner dismissed | Data refreshes, "Back online" toast | P1 |
834| Intermittent connection | Random disconnects | Retry with exponential backoff | Eventually succeeds or clear error | P2 |
835
836**Test with**: Settings → Developer → Network Link Conditioner
837
838### 7.2 Data Validation
839
840| Invalid Input | User Action | Expected Behavior | Verification | Priority |
841|---------------|-------------|-------------------|--------------|----------|
842| Empty required field | Tap Save with empty title | Error message, save disabled | Red outline, "Title is required" | P0 |
843| Invalid email format | Enter "invalid.com" | Inline error shown | "Please enter a valid email" | P0 |
844| Too long input | Enter 500 character title | Character limit enforced | Max 150 chars, counter shows "150/150" | P1 |
845| Special characters | Enter "Item <script>" in title | Sanitized or rejected | No script execution, saved safely | P0 |
846| Duplicate entry | Create item with existing title | Warning shown, allow override | "Item exists. Continue?" | P1 |
847| Invalid date | Select date in past (if not allowed) | Error shown, date reset | "Date must be in future" | P1 |
848
849### 7.3 Permission Handling
850
851| Permission | Denied State | User Action | Expected Behavior | Priority |
852|------------|--------------|-------------|-------------------|----------|
853| Camera | Denied | Tap "Add Photo" | Alert: "Camera access needed. Open Settings?" | P0 |
854| Camera | Denied | User opens Settings | Deep link to app settings | P1 |
855| Photos | Denied | Tap "Choose Photo" | Photo picker not shown, request permission | P0 |
856| Notifications | Denied | App needs to notify | App works without notifications | P1 |
857| Location | Denied | Feature requires location | Feature disabled, explanation shown | P1 |
858
859### 7.4 Device Conditions
860
861| Condition | Expected Behavior | Verification | Priority |
862|-----------|-------------------|--------------|----------|
863| Low battery mode | Reduced animations, background tasks paused | Respects system setting | P1 |
864| Low storage | Can't save images | Error: "Not enough storage" | P1 |
865| Rotation (if supported) | Layout adapts | No clipping, all elements visible | P1 |
866| Multitasking (iPad) | App works in split view | Responsive layout | P2 |
867| Background mode | App suspended, returns | State preserved | P0 |
868| App killed by system | Force quit, relaunch | State restored from last save | P0 |
869
870---
871
872## 8. Security Testing
873
874### 8.1 Data Security
875
876| Test | Action | Expected Result | Priority |
877|------|--------|-----------------|----------|
878| Keychain storage | Store auth token | Token encrypted in Keychain | P0 |
879| API token exposure | Inspect network traffic | Token in Authorization header only | P0 |
880| Sensitive data logging | Check console logs | No passwords/tokens logged | P0 |
881| Screenshot protection | App contains sensitive data | Blur/hide when app switching | P1 |
882
883### 8.2 Privacy
884
885| Test | Action | Expected Result | Priority |
886|------|--------|-----------------|----------|
887| Privacy manifest | Check PrivacyInfo.xcprivacy | All data collection disclosed | P0 |
888| Third-party SDKs | Inspect network requests | No unexpected tracking | P0 |
889| Data export | User requests data | All data provided in readable format | P1 |
890| Account deletion | User deletes account | All data removed from server | P0 |
891
892---
893
894## 9. Regression Testing
895
896Run before every release to ensure new changes don't break existing functionality.
897
898### 9.1 Smoke Test Checklist
899
900**Duration**: 15 minutes
901**Frequency**: Every build
902
903- [ ] App launches without crash
904- [ ] Login/registration works
905- [ ] Home screen loads
906- [ ] Can create new item
907- [ ] Can view item detail
908- [ ] Can edit item
909- [ ] Can delete item
910- [ ] Search works
911- [ ] Settings accessible
912- [ ] Can log out
913
914### 9.2 Full Regression Suite
915
916**Duration**: 2 hours
917**Frequency**: Every release candidate
918
919- [ ] All P0 unit tests pass (100%)
920- [ ] All P0 UI tests pass (100%)
921- [ ] All critical user journeys tested manually
922- [ ] Accessibility audit passed (VoiceOver, Dynamic Type)
923- [ ] Performance benchmarks met
924- [ ] No memory leaks (Instruments check)
925- [ ] Network error scenarios tested
926- [ ] Edge cases from Section 7 verified
927- [ ] Security checklist completed
928- [ ] Privacy manifest up to date
929
930---
931
932## 10. Beta Testing (TestFlight)
933
934### 10.1 Beta Test Plan
935
936**Participants**: 20-50 external beta testers
937**Duration**: 2 weeks before App Store submission
938**Platforms**: iPhone (all sizes), iPad (if supported)
939**iOS Versions**: [minimum deployment target] – latest
940
941**Recruitment**:
942- Internal team members
943- Trusted users from target audience
944- Mix of tech-savvy and average users
945- Geographic diversity (if localized)
946
947**Feedback Collection**:
9481. **In-app feedback form**: Button in Settings → "Send Feedback"
9492. **TestFlight feedback**: Native TestFlight feedback
9503. **Crash reports**: Automatically collected
9514. **Survey**: Email survey after 1 week of use
952
953### 10.2 Beta Testing Focus Areas
954
955**Week 1: Functionality Testing**
956- [ ] All features work as expected
957- [ ] No critical bugs
958- [ ] Performance is acceptable
959- [ ] UI/UX is intuitive
960
961**Week 2: Real-World Usage**
962- [ ] App fits into users' workflow
963- [ ] Identify missing features
964- [ ] Collect feature requests
965- [ ] Assess onboarding clarity
966
967### 10.3 Beta Success Criteria
968
969**Before proceeding to App Store submission**:
970- [ ] Crash-free rate > 99.5%
971- [ ] Average rating from beta testers > 4.0/5.0
972- [ ] No P0 bugs reported in last 3 days
973- [ ] All critical feedback addressed or planned for v1.1
974- [ ] At least 75% of testers completed onboarding
975- [ ] At least 50% of testers used app 3+ times
976
977**Feedback Analysis**:
978- Categorize feedback: bug, feature request, UX issue, question
979- Prioritize: P0 (fix before launch), P1 (fix in v1.1), P2 (backlog)
980- Track resolution: open, in progress, fixed, won't fix
981
982---
983
984## 11. Test Automation
985
986### 11.1 CI/CD Integration
987
988**Platform**: Xcode Cloud / GitHub Actions / Fastlane
989**Trigger**: On every pull request and merge to main
990
991**Pipeline**:
992```yaml
993name: iOS CI
994
995on: [pull_request, push]
996
997jobs:
998 test:
999 runs-on: macos-latest
1000 steps:
1001 - uses: actions/checkout@v3
1002
1003 - name: Select Xcode version
1004 run: sudo xcode-select -s /Applications/Xcode_15.0.app
1005
1006 - name: Run SwiftLint
1007 run: swiftlint lint --strict
1008
1009 - name: Build
1010 run: |
1011 xcodebuild clean build \
1012 -scheme [AppName] \
1013 -destination 'platform=iOS Simulator,name=iPhone 15'
1014
1015 - name: Run Unit Tests
1016 run: |
1017 xcodebuild test \
1018 -scheme [AppName] \
1019 -destination 'platform=iOS Simulator,name=iPhone 15' \
1020 -only-testing:[AppName]Tests
1021
1022 - name: Run UI Tests
1023 run: |
1024 xcodebuild test \
1025 -scheme [AppName] \
1026 -destination 'platform=iOS Simulator,name=iPhone 15' \
1027 -only-testing:[AppName]UITests
1028
1029 - name: Generate Code Coverage
1030 run: |
1031 xcodebuild test \
1032 -scheme [AppName] \
1033 -enableCodeCoverage YES \
1034 -derivedDataPath ./DerivedData
1035
1036 - name: Upload Coverage to Codecov
1037 uses: codecov/codecov-action@v3
1038```
1039
1040### 11.2 Test Reports
1041
1042**Generate HTML test report**:
1043```bash
1044xcodebuild test \
1045 -scheme [AppName] \
1046 -resultBundlePath TestResults.xcresult
1047
1048xcrun xcresulttool get --format html TestResults.xcresult > test-report.html
1049```
1050
1051**Code Coverage Report**:
1052- Target: 80%+ overall coverage
1053- Critical paths: 95%+ coverage (authentication, data persistence)
1054- View in Xcode: Product → Show Code Coverage
1055
1056---
1057
1058## 12. Bug Tracking
1059
1060### 12.1 Bug Report Template
1061
1062**File in issue tracker** (GitHub Issues, Jira, etc.):
1063
1064```markdown
1065**Title**: [Concise, descriptive bug title]
1066
1067**Severity**: Critical / High / Medium / Low
1068
1069**Priority**: P0 / P1 / P2 / P3
1070
1071**Affected Version**: 1.0.0
1072
1073**Environment**:
1074- Device: iPhone 15 Pro
1075- iOS Version: 17.5
1076- App Version: 1.0.0 (Build 42)
1077
1078**Steps to Reproduce**:
10791. Launch app
10802. Navigate to Home
10813. Tap on item "Test Item"
10824. Observe crash
1083
1084**Expected Behavior**:
1085Item detail screen should open without crash.
1086
1087**Actual Behavior**:
1088App crashes immediately when tapping item.
1089
1090**Screenshots/Videos**:
1091[Attach if available]
1092
1093**Crash Logs**:
1094```
1095[Paste crash log from Xcode Organizer]
1096```
1097
1098**Additional Context**:
1099Happens only with items that have nil thumbnailURL.
1100
1101**Possible Fix**:
1102Add nil check in ItemDetailView line 47.
1103```
1104
1105### 12.2 Bug Severity Definitions
1106
1107**Critical (P0)**:
1108- App crashes on launch
1109- Data loss
1110- Security vulnerability
1111- Cannot complete core user flow
1112- Fix immediately, hotfix if needed
1113
1114**High (P1)**:
1115- Feature completely broken
1116- Frequent crashes (> 1% of sessions)
1117- Major UI issues
1118- Fix before next release
1119
1120**Medium (P2)**:
1121- Feature partially broken
1122- Rare crashes (< 0.1% of sessions)
1123- Minor UI issues
1124- Workaround exists
1125- Fix in next minor update
1126
1127**Low (P3)**:
1128- Cosmetic issues
1129- Feature enhancement
1130- Edge case issues
1131- Fix when convenient
1132
1133---
1134
1135## 13. Post-Launch Monitoring
1136
1137### 13.1 Metrics to Track
1138
1139**Crash Analytics** (Xcode Organizer, Firebase Crashlytics):
1140- Crash-free rate: Target > 99.5%
1141- Top crashes by volume
1142- Crash trends over time
1143
1144**Performance Monitoring**:
1145- App launch time: p50, p95, p99
1146- Screen load time
1147- API response time
1148- Memory usage
1149
1150**Usage Analytics** (App Analytics, Firebase Analytics):
1151- Daily/Monthly Active Users (DAU/MAU)
1152- Session length
1153- Session frequency
1154- Feature usage rates
1155- Funnel completion rates
1156
1157**App Store Metrics**:
1158- Rating: Target > 4.5 stars
1159- Reviews: Respond to all reviews
1160- Download trends
1161- Conversion rate (impressions → downloads)
1162
1163**User Feedback**:
1164- Support ticket volume
1165- Common complaints
1166- Feature requests
1167- Positive feedback themes
1168
1169### 13.2 Alerting Thresholds
1170
1171**Critical Alerts** (Page immediately):
1172- Crash rate > 1%
1173- App Store rating drops below 3.0
1174- API error rate > 5%
1175- Server downtime
1176
1177**High Priority Alerts** (Check within 1 hour):
1178- Crash rate > 0.5%
1179- App Store rating drops below 4.0
1180- API error rate > 2%
1181- Support ticket spike
1182
1183**Medium Priority** (Check daily):
1184- Crash rate > 0.1%
1185- Feature usage < 10% of users (for key features)
1186- Session length decreasing trend
1187
1188---
1189
1190## 14. Test Documentation
1191
1192### 14.1 Test Case Format
1193
1194Each test case should document:
1195- **ID**: TC-001
1196- **Feature**: Feature name from PRD
1197- **Priority**: P0/P1/P2
1198- **Type**: Unit/Integration/UI/Manual
1199- **Preconditions**: State before test
1200- **Steps**: Numbered steps
1201- **Expected Result**: Clear pass criteria
1202- **Actual Result**: Filled after execution
1203- **Status**: Pass/Fail/Blocked
1204- **Notes**: Any observations
1205
1206### 14.2 Test Execution Tracking
1207
1208**Test Run Spreadsheet**:
1209
1210| ID | Test Case | Priority | Type | Status | Notes | Date | Tester |
1211|----|-----------|----------|------|--------|-------|------|--------|
1212| TC-001 | User login success | P0 | UI | Pass | | 2024-01-15 | QA |
1213| TC-002 | User login invalid email | P0 | UI | Pass | | 2024-01-15 | QA |
1214| TC-003 | Create item success | P0 | UI | Fail | Crash on save | 2024-01-15 | QA |
1215
1216**Test Coverage Matrix**:
1217
1218| Feature (PRD) | Unit Tests | Integration Tests | UI Tests | Manual Tests | Coverage % |
1219|---------------|------------|-------------------|----------|--------------|------------|
1220| User registration | 12/12 ✅ | 3/3 ✅ | 2/2 ✅ | 5/5 ✅ | 100% |
1221| Item CRUD | 20/20 ✅ | 5/5 ✅ | 8/10 ⚠️ | 10/10 ✅ | 90% |
1222| Search | 8/8 ✅ | 2/2 ✅ | 3/3 ✅ | 3/3 ✅ | 100% |
1223
1224---
1225
1226## 15. Release Readiness Checklist
1227
1228Before submitting to App Store:
1229
1230### 15.1 Testing Complete
1231- [ ] All P0 unit tests pass (100%)
1232- [ ] All P1 unit tests pass (> 95%)
1233- [ ] All P0 UI tests pass (100%)
1234- [ ] No P0 bugs open
1235- [ ] No P1 bugs open (or accepted as known issues)
1236- [ ] Code coverage > 80%
1237- [ ] Performance benchmarks met
1238- [ ] Memory leaks checked (0 leaks)
1239- [ ] Accessibility audit passed
1240- [ ] Security audit passed
1241
1242### 15.2 Manual Testing
1243- [ ] Full regression suite completed
1244- [ ] All critical user journeys tested on real devices
1245- [ ] Tested on iPhone SE (smallest screen)
1246- [ ] Tested on iPhone Pro Max (largest screen)
1247- [ ] Tested on iPad (if supported)
1248- [ ] Tested on iOS 17.0 (minimum version)
1249- [ ] Tested on latest iOS version
1250- [ ] Dark mode tested
1251- [ ] Localization tested (all supported languages)
1252
1253### 15.3 Beta Testing
1254- [ ] TestFlight beta completed (2 weeks)
1255- [ ] Crash-free rate > 99.5%
1256- [ ] Beta tester rating > 4.0/5.0
1257- [ ] All critical beta feedback addressed
1258- [ ] No new crashes in last 48 hours
1259
1260### 15.4 Final Checks
1261- [ ] App Store assets ready (from RELEASE_SPEC)
1262- [ ] Privacy manifest accurate
1263- [ ] App Store guidelines compliance verified
1264- [ ] Legal review completed (terms, privacy policy)
1265- [ ] Marketing materials ready
1266- [ ] Support infrastructure ready (email, FAQ)
1267
1268---
1269
1270## Appendix A: Test Data
1271
1272### A.1 Test Users
1273
1274```json
1275{
1276 "valid_user": {
1277 "name": "Test User",
1278 "email": "test@example.com",
1279 "password": "Password123!"
1280 },
1281 "invalid_email": {
1282 "name": "Invalid",
1283 "email": "invalid.com",
1284 "password": "Password123!"
1285 },
1286 "empty_fields": {
1287 "name": "",
1288 "email": "",
1289 "password": ""
1290 }
1291}
1292```
1293
1294### A.2 Test Items
1295
1296```json
1297{
1298 "valid_item": {
1299 "title": "Test Item",
1300 "description": "This is a test description",
1301 "status": "active"
1302 },
1303 "long_title": {
1304 "title": "[150 character string...]",
1305 "description": "Normal description",
1306 "status": "active"
1307 },
1308 "special_chars": {
1309 "title": "Item with <script>alert('XSS')</script>",
1310 "description": "Testing sanitization",
1311 "status": "active"
1312 }
1313}
1314```
1315
1316---
1317
1318## Appendix B: Glossary
1319
1320- **P0**: Critical priority - must be fixed before release
1321- **P1**: High priority - should be fixed before release
1322- **P2**: Medium priority - can be deferred to next release
1323- **P3**: Low priority - backlog
1324- **SUT**: System Under Test
1325- **Mock**: Simulated object for testing
1326- **Stub**: Predefined test data
1327- **Regression**: Re-testing to ensure new changes don't break existing functionality
1328- **Smoke Test**: Quick test of basic functionality
1329- **Sanity Test**: Quick test after bug fix to verify it's resolved
1330- **Coverage**: Percentage of code executed by tests
1331
1332---
1333
1334**Document History**:
1335| Version | Date | Author | Changes |
1336|---------|------|--------|---------|
1337| 1.0.0 | [Date] | QA Engineer AI | Initial test specification created |
1338```
1339
1340---
1341
1342## Execution Instructions
1343
1344When activated, follow these steps:
1345
13461. **Read All Specification Documents**
1347 ```
1348 - Read docs/PRD.md (features, acceptance criteria, user stories)
1349 - Read docs/IMPLEMENTATION_GUIDE.md (all ViewModels, models, services)
1350 - Read docs/UX_SPEC.md (all screens, user flows, states)
1351 - Read docs/ARCHITECTURE.md (testing strategy)
1352 ```
1353
13542. **Extract Testing Requirements**
1355 - List all features to test (from PRD)
1356 - List all ViewModels to unit test (from IMPLEMENTATION_GUIDE)
1357 - List all models to unit test
1358 - List all user flows to UI test (from UX_SPEC)
1359 - Identify edge cases from UX_SPEC error states
1360
13613. **Generate Unit Test Cases**
1362 - For each Model: test initialization, validation, methods, relationships, Codable
1363 - For each ViewModel: test loading, errors, user actions, state management
1364 - For each Service: test API calls, error handling, data operations
1365
13664. **Generate Integration Test Cases**
1367 - Database + API sync
1368 - ViewModel + Service integration
1369 - Multi-component workflows
1370
13715. **Generate UI Test Cases**
1372 - Critical user journeys (from PRD P0 features)
1373 - Include all steps with assertions
1374 - Provide complete XCUITest implementation
1375
13766. **Generate Accessibility Tests**
1377 - VoiceOver labels for all interactive elements
1378 - Dynamic Type scenarios
1379 - Color contrast checks
1380
13817. **Generate Performance Tests**
1382 - Launch time benchmarks
1383 - Load testing scenarios
1384 - Memory usage targets
1385
13868. **Document Edge Cases**
1387 - Network conditions (offline, slow, timeout)
1388 - Data validation (empty, invalid, too long)
1389 - Permission handling (denied, restricted)
1390 - Device conditions (low battery, low storage)
1391
13929. **Create Beta Testing Plan**
1393 - Participant criteria
1394 - Duration and timeline
1395 - Feedback collection methods
1396 - Success criteria
1397
139810. **Write Complete Test Spec**
1399 ```
1400 Write to: docs/TEST_SPEC.md
1401 Target length: 2000-3000 lines
1402 Format: Markdown with code examples
1403 ```
1404
140511. **Present Summary**
1406 ```
1407 ✅ Test Specification generated!
1408
1409 🧪 **Test Spec Summary**:
1410 - Unit test cases: [X] tests across [Y] test suites
1411 - Integration test cases: [Z] scenarios
1412 - UI test cases: [N] critical user journeys with full implementation
1413 - Accessibility tests: VoiceOver, Dynamic Type, Contrast
1414 - Performance benchmarks: Launch time, memory, FPS
1415 - Edge cases documented: [M] scenarios
1416 - Beta testing plan: 2-week plan with [P] participants
1417
1418 **Coverage**:
1419 - All features from PRD have test cases
1420 - All ViewModels have unit tests
1421 - All critical flows have UI tests
1422 - Accessibility fully covered
1423 - Performance targets defined
1424
1425 **Next Steps**:
1426 1. Review test cases in docs/TEST_SPEC.md
1427 2. Implement unit tests as development progresses
1428 3. Set up CI/CD pipeline for automated testing
1429 4. Execute manual test cases before release
1430 5. Run beta test program via TestFlight
1431
1432 **Developer can now**:
1433 - Write tests alongside implementation
1434 - Achieve 80%+ code coverage
1435 - Ensure all features are tested
1436 - Catch bugs before release
1437
1438 Ready to proceed to release specification?
1439 ```
1440
144112. **Iterate Based on Feedback**
1442 If user requests changes:
1443 - Add missing test cases
1444 - Expand specific areas (e.g., more edge cases)
1445 - Provide more implementation examples
1446 - Adjust priorities
1447
1448---
1449
1450## Quality Guidelines
1451
14521. **Be Comprehensive**: Cover every feature from PRD
1453 - Unit tests for all business logic
1454 - UI tests for all critical flows
1455 - Integration tests for key workflows
1456 - Edge cases for all error scenarios
1457
14582. **Be Specific**: Test cases must be actionable
1459 - Clear steps
1460 - Clear expected results
1461 - Clear pass/fail criteria
1462 - Include test data
1463
14643. **Provide Code Examples**: Not just test case descriptions
1465 - Show complete XCTest implementations
1466 - Show XCUITest implementations
1467 - Show mock objects
1468 - Show test setup/teardown
1469
14704. **Think Like QA**: What could go wrong?
1471 - Empty states
1472 - Invalid inputs
1473 - Network failures
1474 - Permission denials
1475 - Edge cases
1476
14775. **Consider Real Users**: Beta testing is crucial
1478 - Real devices, real usage patterns
1479 - Diverse user base
1480 - Real-world network conditions
1481 - Actual user feedback
1482
14836. **Enable Automation**: CI/CD integration
1484 - Tests should run automatically
1485 - Fast feedback loop
1486 - Code coverage tracking
1487 - Fail fast on critical issues
1488
1489---
1490
1491## Integration with Workflow
1492
1493This skill is typically:
1494- **Fifth step** in specification generation
1495- Activated after PRD, Architecture, UX, and Implementation Guide are complete
1496- Followed by release-spec
1497- Critical for ensuring quality before App Store submission
1498
1499The test specification ensures the app works correctly and provides confidence for release.
1500
1501---
1502
1503## Notes
1504
1505- Every feature in PRD must have corresponding test cases
1506- Test early, test often - don't wait until the end
1507- Automated tests save time in the long run
1508- Manual testing still crucial for UX validation
1509- Beta testing provides real-world feedback
1510- Bug tracking is part of the testing process
1511- Performance and accessibility are not optional
1512- Security testing must be thorough
1513- Document all test cases for repeatability