«Primeiro o teste — depois o código»: por que isso funciona em Swift?
\n\nVocê já reescreveu o mesmo módulo pela terceira vez porque «precisava lançar com urgência»? Ou já pegou um bug em produção que era mais fácil prevenir na fase de escrita? Se sim — bem-vindo ao mundo do TDD (Test-Driven Development).
\n\nDesenvolvimento orientado a testes não é apenas uma metodologia da moda. É uma forma de pensar que faz você formular como o código deve se comportar antes de escrevê-lo. Em Swift, com sua tipagem forte e o poderoso framework XCTest, o TDD se transforma em um escudo confiável contra regressões e falhas inesperadas.
Neste artigo, vamos explorar o TDD com exemplos reais, escrever testes para a camada de rede e modelo de dados e, principalmente, entender como implementar essa prática sem dor. Vamos lá!
\n\nVermelho-Verde-Refatoração: a magia do ciclo
\n\nA base do TDD é um ciclo de três etapas:
\n\n- \n
- Vermelho (Red) — escrevemos um teste que não passa (porque o código ainda não existe). \n
- Verde (Green) — escrevemos o código mínimo para o teste passar. \n
- Refatoração (Refactor) — melhoramos o código sem alterar seu comportamento (os testes protegem). \n
Parece simples? Vamos tentar em Swift.
\n\nPrimeiro teste: verificando o modelo User
\n\nSuponha que temos um modelo simples de usuário. Queremos que ele formate corretamente o nome completo. Primeiro — o teste:
\n\nimport XCTest\n@testable import MyApp\n\nfinal class UserTests: XCTestCase {\n \n func testFullName_GivenFirstNameAndLastName_ReturnsFormattedString() {\n // Arrange\n let user = User(firstName: "Ana", lastName: "Karenina")\n \n // Act\n let fullName = user.fullName()\n \n // Assert\n XCTAssertEqual(fullName, "Ana Karenina", "O nome completo deve consistir no nome e sobrenome separados por espaço")\n }\n}\n\n\nExecutamos — o teste falha (vermelho). O compilador reclama que User não existe. Ótimo, é exatamente o que queremos.
Agora escrevemos o código mínimo para o teste passar:
\n\nstruct User {\n let firstName: String\n let lastName: String\n \n func fullName() -> String {\n return "\\(firstName) \\(lastName)"\n }\n}\n\n\nExecutamos — verde! Podemos passar para a refatoração. Por exemplo, adicionar tratamento para strings vazias ou nome do meio. Mas não se apresse — os testes indicarão quando isso for necessário.
\n\nTestando a camada de rede assíncrona
\n\nUm dos problemas mais comuns no desenvolvimento iOS é o trabalho com rede. O TDD é especialmente útil aqui, pois você separa claramente a lógica das chamadas de rede.
\n\nEscrevendo o protocolo e o objeto mock
\n\nPrimeiro, definimos como nosso serviço deve ser:
\n\nprotocol WeatherServiceProtocol {\n func fetchWeather(for city: String, completion: @escaping (Result<Weather, Error>) -> Void)\n}\n\n\nAgora — o teste. Não queremos depender de uma API real, então criamos um 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 // Arrange\n let mockService = MockWeatherService()\n mockService.mockWeather = Weather(temperature: 25.0, condition: "sunny")\n let viewModel = WeatherViewModel(service: mockService)\n let expectation = XCTestExpectation(description: "Aguardando carregamento do clima")\n \n