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
  • 案例:测试 Todo 应用的逻辑

案例:测试 Todo 应用的逻辑

测试目标

在本案例中,我们将为之前实现的 Todo 应用编写单元测试和 UI 测试,覆盖以下核心逻辑:

  1. Model 层的数据验证
  2. ViewModel 的业务逻辑
  3. View 层的交互行为

单元测试 Model 和 ViewModel

1. 测试 Todo 模型

import XCTest
@testable import TodoApp

class TodoModelTests: XCTestCase {
    func testTodoInitialization() {
        let todo = Todo(id: UUID(), title: "Buy milk", isCompleted: false)
        XCTAssertEqual(todo.title, "Buy milk")
        XCTAssertFalse(todo.isCompleted)
    }
    
    func testTodoToggleCompletion() {
        var todo = Todo(title: "Write tests")
        todo.isCompleted.toggle()
        XCTAssertTrue(todo.isCompleted)
    }
}

2. 测试 ViewModel 逻辑

class TodoViewModelTests: XCTestCase {
    var viewModel: TodoViewModel!
    
    override func setUp() {
        viewModel = TodoViewModel(dataService: MockDataService())
    }
    
    func testAddTodo() {
        viewModel.addTodo(title: "New task")
        XCTAssertEqual(viewModel.todos.count, 1)
        XCTAssertEqual(viewModel.todos[0].title, "New task")
    }
    
    func testDeleteTodo() {
        let todo = Todo(title: "To be deleted")
        viewModel.todos = [todo]
        viewModel.deleteTodo(at: IndexSet([0]))
        XCTAssertTrue(viewModel.todos.isEmpty)
    }
}

UI 测试实现

1. 测试列表交互

import XCTest

class TodoUITests: XCTestCase {
    var app: XCUIApplication!
    
    override func setUp() {
        app = XCUIApplication()
        app.launchArguments = ["-ui-testing"]
        app.launch()
    }
    
    func testAddTodoFlow() {
        // 点击添加按钮
        app.navigationBars["My Todos"].buttons["Add"].tap()
        
        // 在文本框中输入
        let textField = app.textFields["todo_title_field"]
        textField.tap()
        textField.typeText("UI Test Task")
        
        // 点击保存
        app.buttons["Save"].tap()
        
        // 验证新条目出现
        XCTAssertTrue(app.staticTexts["UI Test Task"].exists)
    }
}

Mock 数据与依赖注入

1. 创建 Mock 服务

class MockDataService: DataServiceProtocol {
    private(set) var todos: [Todo] = []
    
    func fetchTodos() -> [Todo] {
        return [Todo(title: "Mock Task 1"), Todo(title: "Mock Task 2")]
    }
    
    func saveTodos(_ todos: [Todo]) {
        self.todos = todos
    }
}

2. 在测试中使用 Mock

func testViewModelWithMockData() {
    let mockService = MockDataService()
    let vm = TodoViewModel(dataService: mockService)
    
    XCTAssertEqual(vm.todos.count, 2)
    XCTAssertEqual(vm.todos[0].title, "Mock Task 1")
}

测试覆盖率优化技巧

  1. 边界条件测试:

    • 测试空任务标题的处理
    • 测试批量删除操作
  2. 性能测试:

func testPerformanceOnLargeDataset() {
    measure {
        viewModel.todos = (1...1000).map { Todo(title: "Task \($0)") }
        viewModel.saveTodos()
    }
}
  1. 快照测试(需集成 iOSSnapshotTestCase):
func testTodoListAppearance() {
    let view = TodoListView(viewModel: testViewModel)
    verifyView(view)
}

常见问题解决

❌ 问题1:测试时遇到真实网络请求
✅ 解决方案:始终使用依赖注入和 Mock 服务

❌ 问题2:UI 测试元素找不到
✅ 解决方案:

  1. 为所有交互元素设置 accessibilityIdentifier
  2. 增加等待时间:app.buttons["Submit"].waitForExistence(timeout: 5)

❌ 问题3:测试间状态污染
✅ 解决方案:在 setUp() 中重置状态,在 tearDown() 中清理资源

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