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
  • 第7章:测试 MVVM 应用

第7章:测试 MVVM 应用

单元测试 Model 和 ViewModel

1. 单元测试的重要性

在 MVVM 架构中,Model 和 ViewModel 是业务逻辑的核心承载层。通过单元测试可以:

  • 验证数据模型的正确性(如属性、计算逻辑)
  • 确保 ViewModel 的行为符合预期(如状态更新、事件响应)
  • 提高代码可维护性,减少回归错误

2. 测试 Model 层

测试要点:

struct TodoItem: Identifiable {
    let id: UUID
    var title: String
    var isCompleted: Bool
    
    // 测试计算属性示例
    var statusDescription: String {
        isCompleted ? "已完成" : "未完成"
    }
}

// 测试用例
class TodoModelTests: XCTestCase {
    func testStatusDescription() {
        let item1 = TodoItem(id: UUID(), title: "任务1", isCompleted: true)
        XCTAssertEqual(item1.statusDescription, "已完成")
        
        let item2 = TodoItem(id: UUID(), title: "任务2", isCompleted: false)
        XCTAssertEqual(item2.statusDescription, "未完成")
    }
}

测试策略:

  • 验证数据初始状态
  • 测试自定义方法/计算属性
  • 边界条件测试(如空值、极端值)

3. 测试 ViewModel 层

测试环境搭建:

class TodoListViewModel: ObservableObject {
    @Published var items: [TodoItem] = []
    
    func addItem(title: String) {
        let newItem = TodoItem(id: UUID(), title: title, isCompleted: false)
        items.append(newItem)
    }
    
    func toggleCompletion(for item: TodoItem) {
        if let index = items.firstIndex(where: { $0.id == item.id }) {
            items[index].isCompleted.toggle()
        }
    }
}

class TodoViewModelTests: XCTestCase {
    var viewModel: TodoListViewModel!
    
    override func setUp() {
        super.setUp()
        viewModel = TodoListViewModel()
    }
}

核心测试场景:

// 测试添加功能
func testAddItem() {
    let initialCount = viewModel.items.count
    viewModel.addItem(title: "测试任务")
    XCTAssertEqual(viewModel.items.count, initialCount + 1)
    XCTAssertEqual(viewModel.items.last?.title, "测试任务")
}

// 测试状态切换
func testToggleCompletion() {
    viewModel.addItem(title: "测试任务")
    let item = viewModel.items[0]
    
    // 初始状态验证
    XCTAssertFalse(item.isCompleted)
    
    // 第一次切换
    viewModel.toggleCompletion(for: item)
    XCTAssertTrue(viewModel.items[0].isCompleted)
    
    // 第二次切换
    viewModel.toggleCompletion(for: viewModel.items[0])
    XCTAssertFalse(viewModel.items[0].isCompleted)
}

4. 高级测试技巧

异步操作测试:

func testAsyncDataLoading() {
    let expectation = XCTestExpectation(description: "Data loaded")
    
    // 模拟网络请求
    DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
        self.viewModel.addItem(title: "异步任务")
        expectation.fulfill()
    }
    
    wait(for: [expectation], timeout: 2)
    XCTAssertFalse(viewModel.items.isEmpty)
}

依赖注入测试:

protocol DataService {
    func fetchTodos() -> [TodoItem]
}

class MockDataService: DataService {
    func fetchTodos() -> [TodoItem] {
        return [TodoItem(id: UUID(), title: "Mock任务", isCompleted: true)]
    }
}

class ViewModelWithDependency {
    let service: DataService
    @Published var items: [TodoItem] = []
    
    init(service: DataService) {
        self.service = service
    }
    
    func loadData() {
        items = service.fetchTodos()
    }
}

func testDependencyInjection() {
    let mockService = MockDataService()
    let vm = ViewModelWithDependency(service: mockService)
    
    vm.loadData()
    XCTAssertEqual(vm.items.count, 1)
    XCTAssertEqual(vm.items[0].title, "Mock任务")
}

5. 测试最佳实践

  1. 命名规范:使用 test[被测单元]_[条件]_[预期结果] 格式
  2. 测试隔离:每个测试方法应该是独立的
  3. 断言明确:避免通用断言,应验证具体值
  4. 测试覆盖率:优先覆盖核心业务逻辑
  5. CI 集成:将测试纳入持续集成流程

6. 常见问题解决方案

问题类型解决方案
测试随机失败使用固定种子或模拟随机数生成
异步测试超时合理设置 timeout 值
状态污染在 setUp/tearDown 中重置状态
复杂依赖使用协议抽象依赖

通过系统化的单元测试,可以显著提升 MVVM 应用的可靠性和开发效率。建议结合 Xcode 的 Test Navigator 和 Code Coverage 工具持续优化测试质量。

Last Updated:: 4/25/25, 8:19 PM