案例:测试 Todo 应用的逻辑
测试目标
在本案例中,我们将为之前实现的 Todo 应用编写单元测试和 UI 测试,覆盖以下核心逻辑:
- Model 层的数据验证
- ViewModel 的业务逻辑
- 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")
}
测试覆盖率优化技巧
边界条件测试:
- 测试空任务标题的处理
- 测试批量删除操作
性能测试:
func testPerformanceOnLargeDataset() {
measure {
viewModel.todos = (1...1000).map { Todo(title: "Task \($0)") }
viewModel.saveTodos()
}
}
- 快照测试(需集成 iOSSnapshotTestCase):
func testTodoListAppearance() {
let view = TodoListView(viewModel: testViewModel)
verifyView(view)
}
常见问题解决
❌ 问题1:测试时遇到真实网络请求
✅ 解决方案:始终使用依赖注入和 Mock 服务
❌ 问题2:UI 测试元素找不到
✅ 解决方案:
- 为所有交互元素设置
accessibilityIdentifier - 增加等待时间:
app.buttons["Submit"].waitForExistence(timeout: 5)
❌ 问题3:测试间状态污染
✅ 解决方案:在 setUp() 中重置状态,在 tearDown() 中清理资源
