Swift中的TDD:编写不会崩溃的代码

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

“先测试——后代码”:为什么这在Swift中有效?

\n\n

您是否曾经因为“需要紧急上线”而第三次重写同一个模块?或者在生产环境中捕获到一个本可以在编写阶段就预防的bug?如果是这样——欢迎来到TDD(测试驱动开发)的世界。

\n\n

测试驱动开发不仅仅是一种时髦的方法论。它是一种思维方式,迫使您在编写代码之前就明确代码应该如何表现。在Swift中,凭借其严格的类型检查和强大的XCTest框架,TDD变成了抵御回归和意外崩溃的可靠盾牌。

\n\n

在本文中,我们将通过真实示例剖析TDD,为网络层和数据模型编写测试,最重要的是——理解如何无痛地实践这一方法。开始吧!

\n\n

红-绿-重构:循环的魔力

\n\n

TDD的基础是一个三步循环:

\n\n
    \n
  • 红色(Red)——编写一个无法通过的测试(因为代码还不存在)。
  • \n
  • 绿色(Green)——编写最少的代码使测试通过。
  • \n
  • 重构(Refactor)——改进代码,不改变其行为(测试提供保护)。
  • \n
\n\n

看起来很简单?让我们在Swift中尝试一下。

\n\n

第一个测试:验证User模型

\n\n

假设我们有一个简单的用户模型。我们希望它能正确格式化全名。首先——测试:

\n\n
import 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\n

现在编写最少的代码使测试通过:

\n\n
struct 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\n

iOS开发中最常见的问题之一——网络操作。TDD在这里特别有用,因为您可以明确地将逻辑与网络调用分离。

\n\n

编写协议和mock对象

\n\n

首先,定义我们的服务应该是什么样子:

\n\n
protocol WeatherServiceProtocol {\n    func fetchWeather(for city: String, completion: @escaping (Result<Weather, Error>) -> Void)\n}\n
\n\n

现在——测试。我们不想依赖真实的API,因此创建一个mock:

\n\n
class 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                

博客

书籍推荐