Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
  • 第 10 章:测试 SwiftData 应用

第 10 章:测试 SwiftData 应用

单元测试:使用内存存储进行快速测试

为什么需要单元测试 SwiftData 应用?

  • 验证数据模型的正确性(属性、关系、业务逻辑)
  • 确保 CRUD 操作按预期工作
  • 提前发现数据迁移问题
  • 提高代码可维护性和重构安全性

配置测试环境

import XCTest
import SwiftData
@testable import YourAppName

final class YourDataModelTests: XCTestCase {
    var modelContainer: ModelContainer!
    var modelContext: ModelContext!
    
    override func setUp() {
        super.setUp()
        // 配置内存中的临时存储
        let config = ModelConfiguration(isStoredInMemoryOnly: true)
        modelContainer = try! ModelContainer(for: YourModel.self, configurations: config)
        modelContext = ModelContext(modelContainer)
    }
    
    override func tearDown() {
        modelContext = nil
        modelContainer = nil
        super.tearDown()
    }
}

核心测试场景

1. 测试模型创建与保存

func testCreateAndSaveItem() throws {
    let testItem = Item(name: "Test", timestamp: Date())
    modelContext.insert(testItem)
    try modelContext.save()
    
    let fetchDescriptor = FetchDescriptor<Item>()
    let items = try modelContext.fetch(fetchDescriptor)
    XCTAssertEqual(items.count, 1)
    XCTAssertEqual(items.first?.name, "Test")
}

2. 测试删除操作

func testDeleteItem() throws {
    let testItem = Item(name: "ToDelete", timestamp: Date())
    modelContext.insert(testItem)
    try modelContext.save()
    
    modelContext.delete(testItem)
    try modelContext.save()
    
    let fetchDescriptor = FetchDescriptor<Item>()
    let items = try modelContext.fetch(fetchDescriptor)
    XCTAssertTrue(items.isEmpty)
}

3. 测试关系操作

func testOneToManyRelationship() throws {
    let parent = Parent(name: "Parent")
    let child = Child(name: "Child")
    parent.children.append(child)
    
    modelContext.insert(parent)
    try modelContext.save()
    
    let fetchedParent = try modelContext.fetch(FetchDescriptor<Parent>()).first!
    XCTAssertEqual(fetchedParent.children.count, 1)
    XCTAssertEqual(fetchedParent.children.first?.name, "Child")
}

高级测试技巧

使用 Predicate 测试查询

func testPredicateQuery() throws {
    let item1 = Item(name: "Important", timestamp: Date())
    let item2 = Item(name: "Trivial", timestamp: Date())
    modelContext.insert(item1)
    modelContext.insert(item2)
    try modelContext.save()
    
    let predicate = #Predicate<Item> { $0.name == "Important" }
    let descriptor = FetchDescriptor<Item>(predicate: predicate)
    let results = try modelContext.fetch(descriptor)
    
    XCTAssertEqual(results.count, 1)
    XCTAssertEqual(results.first?.name, "Important")
}

测试自定义模型方法

// 假设 Item 模型有一个计算属性
extension Item {
    var isImportant: Bool { name.contains("Important") }
}

func testCustomModelLogic() throws {
    let item = Item(name: "Important Task", timestamp: Date())
    XCTAssertTrue(item.isImportant)
}

测试最佳实践

  1. 隔离性:每个测试应该独立运行,不依赖其他测试的状态
  2. 速度优化:使用内存存储(isStoredInMemoryOnly: true)避免磁盘 I/O
  3. 确定性:避免使用随机数据,确保测试可重复
  4. 清理资源:在 tearDown 中释放所有测试资源
  5. 边界测试:特别测试空值、极值等边界情况

常见问题解决方案

问题:测试间数据污染

// 解决方案:在每个测试后清空上下文
override func tearDown() {
    let items = try? modelContext.fetch(FetchDescriptor<Item>())
    items?.forEach { modelContext.delete($0) }
    try? modelContext.save()
    super.tearDown()
}

问题:测试异步操作

func testAsyncOperation() {
    let expectation = XCTestExpectation(description: "Async save completion")
    
    DispatchQueue.global().async {
        let newItem = Item(name: "Async", timestamp: Date())
        self.modelContext.insert(newItem)
        try! self.modelContext.save()
        expectation.fulfill()
    }
    
    wait(for: [expectation], timeout: 1.0)
}
Last Updated:: 5/30/25, 5:48 PM