skills/ → skills-dev(9), skills-req(10), skills-ops(4), skills-integration(8), skills-biz(4), skills-workflow(7) generate-marketplace.py 改为自动扫描所有 skills-* 目录。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.1 KiB
4.1 KiB
iOS 测试 (XCTest + Swift Concurrency)
测试框架
- XCTest: Apple 官方测试框架
- Swift Testing: Swift 6 新测试框架 (可选)
- ViewInspector: SwiftUI 视图测试 (第三方)
运行测试
# 全部测试
xcodebuild test \
-scheme AI-Proj-iOS \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-quiet
# 特定测试类
xcodebuild test \
-scheme AI-Proj-iOS \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-only-testing:AI-Proj-iOSTests/DashboardViewModelTests
# 覆盖率
xcodebuild test \
-scheme AI-Proj-iOS \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-enableCodeCoverage YES
项目测试结构 (AI-Proj-iOS)
AI-Proj-iOSTests/
├── Mocks/
│ ├── MockServices.swift # Mock 服务协议实现
│ └── MockNetworkService.swift
├── ViewModels/
│ ├── DashboardViewModelTests.swift
│ ├── TaskViewModelTests.swift
│ └── RequirementViewModelTests.swift
├── Services/
│ ├── TaskServiceTests.swift
│ └── DashboardAggregationServiceTests.swift
├── Models/
│ └── ModelDecodingTests.swift
└── Utilities/
└── DateFormatterTests.swift
关键模式
1. Mock 服务 — Result 注入
class MockTaskService: TaskServiceProtocol {
var fetchTasksResult: Result<TaskListResponse, Error> = .success(.mock)
func fetchTasks(...) async throws -> TaskListResponse {
switch fetchTasksResult {
case .success(let response): return response
case .failure(let error): throw error
}
}
}
所有 Mock 服务统一用 Result 属性控制成功/失败返回。
2. ViewModel 测试 — @MainActor + async
@MainActor
final class DashboardViewModelTests: XCTestCase {
var sut: DashboardViewModel!
var mockService: MockDashboardAggregationService!
override func setUp() {
super.setUp()
mockService = MockDashboardAggregationService()
sut = DashboardViewModel(dashboardService: mockService)
}
override func tearDown() {
sut = nil; mockService = nil
super.tearDown()
}
func testLoadDashboardData_Success() async {
mockService.fetchDashboardDataResult = .success(expectedData)
await sut.loadDashboardData()
XCTAssertFalse(sut.isLoading)
XCTAssertEqual(sut.todayStats.completedTasks, 5)
}
}
要点:@MainActor + async 测试方法 + setUp/tearDown 重置。
3. Mock 数据工厂 — 静态 .mock() 方法
extension TaskModel {
static func mock(id: Int = 1, status: TaskStatus = .todo) -> TaskModel {
TaskModel(id: id, title: "Mock Task", status: status, ...)
}
}
extension TaskListResponse {
static var mock: TaskListResponse {
TaskListResponse(tasks: [.mock(id: 1), .mock(id: 2)], total: 2, page: 1, pageSize: 20)
}
}
4. 模型解码测试 — JSON → Model
func testTaskModel_DecodesFromJSON() throws {
let json = """
{ "id": 123, "status": "in_progress", "priority": "high", ... }
""".data(using: .utf8)!
let task = try decoder.decode(TaskModel.self, from: json)
XCTAssertEqual(task.status, .inProgress)
}
5. SwiftUI 视图测试 — ViewInspector
extension EnhancedStatsSection: Inspectable {}
func testStatsSection_DisplaysCorrectValues() throws {
let view = EnhancedStatsSection(stats: .mock)
let text = try view.inspect().find(text: "5")
XCTAssertNotNil(text)
}
最佳实践
- @MainActor — ViewModel 测试必须在主线程
- Mock 所有依赖 — 协议抽象 + Result 注入
- async/await — 避免 XCTestExpectation 回调
- 数据工厂 —
.mock()静态方法,参数带默认值 - 隔离测试 — setUp/tearDown 重置所有状态
- 命名 —
test<Method>_<Scenario>格式
Xcode 快捷键
| 快捷键 | 操作 |
|---|---|
Cmd + U |
运行所有测试 |
Ctrl + Opt + Cmd + U |
运行当前测试方法 |
Ctrl + Opt + Cmd + G |
重新运行上次测试 |
Cmd + 6 |
Test Navigator |