“先测试——后代码”:为什么这在Swift中有效?
\n\n您是否曾经因为“需要紧急上线”而第三次重写同一个模块?或者在生产环境中捕获到一个本可以在编写阶段就预防的bug?如果是这样——欢迎来到TDD(测试驱动开发)的世界。
\n\n测试驱动开发不仅仅是一种时髦的方法论。它是一种思维方式,迫使您在编写代码之前就明确代码应该如何表现。在Swift中,凭借其严格的类型检查和强大的XCTest框架,TDD变成了抵御回归和意外崩溃的可靠盾牌。
在本文中,我们将通过真实示例剖析TDD,为网络层和数据模型编写测试,最重要的是——理解如何无痛地实践这一方法。开始吧!
\n\n红-绿-重构:循环的魔力
\n\nTDD的基础是一个三步循环:
\n\n- \n
- 红色(Red)——编写一个无法通过的测试(因为代码还不存在)。 \n
- 绿色(Green)——编写最少的代码使测试通过。 \n
- 重构(Refactor)——改进代码,不改变其行为(测试提供保护)。 \n
看起来很简单?让我们在Swift中尝试一下。
\n\n第一个测试:验证User模型
\n\n假设我们有一个简单的用户模型。我们希望它能正确格式化全名。首先——测试:
\n\nimport XCTest\n@testable import MyApp\n\nfinal class UserTests: XCTestCase {\n \n func testFullName_GivenFirstNameAndLastName_ReturnsFormattedString() {\n // 准备\n let user = User(firstName: "安娜", lastName: "卡列尼娜")\n \n // 执行\n let fullName = user.fullName()\n \n // 断言\n XCTAssertEqual(fullName, "安娜 卡列尼娜", "全名应由空格分隔的名和姓组成")\n }\n}\n\n\n运行——测试失败(红色)。编译器报错说User不存在。很好,这正是预期的。
现在编写最少的代码使测试通过:
\n\nstruct User {\n let firstName: String\n let lastName: String\n \n func fullName() -> String {\n return "\\(firstName) \\(lastName)"\n }\n}\n\n\n运行——绿色!可以进入重构阶段了。例如,添加对空字符串或中间名的处理。但别急——测试会告诉您何时需要这样做。
\n\n测试异步网络层
\n\niOS开发中最常见的问题之一——网络操作。TDD在这里特别有用,因为您可以明确地将逻辑与网络调用分离。
\n\n编写协议和mock对象
\n\n首先,定义我们的服务应该是什么样子:
\n\nprotocol WeatherServiceProtocol {\n func fetchWeather(for city: String, completion: @escaping (Result<Weather, Error>) -> Void)\n}\n\n\n现在——测试。我们不想依赖真实的API,因此创建一个mock:
\n\nclass MockWeatherService: WeatherServiceProtocol {\n var shouldReturnError = false\n var mockWeather: Weather?\n \n func fetchWeather(for city: String, completion: @escaping (Result<Weather, Error>) -> Void) {\n if shouldReturnError {\n completion(.failure(NetworkError.serverError))\n } else if let weather = mockWeather {\n completion(.success(weather))\n }\n }\n}\n\nfinal class WeatherViewModelTests: XCTestCase {\n \n func testFetchWeather_Success_UpdatesModel() {\n // 准备\n let mockService = MockWeatherService()\n mockService.mockWeather = Weather(temperature: 25.0, condition: "sunny")\n let viewModel = WeatherViewModel(service: mockService)\n let expectation = XCTestExpectation(description: "等待天气数据加载")\n \n