Introdução: Por que os testes são a base de um aplicativo Swift confiável
O desenvolvimento para iOS e macOS exige não apenas uma interface bonita, mas também uma lógica estável. Testes em Swift não são uma opção, mas uma necessidade. Sem testes, até mesmo uma pequena alteração pode quebrar funcionalidades que funcionavam há anos. Neste artigo, vamos analisar três níveis de teste: unit (teste de unidade), integration (teste de integração) e e2e (teste ponta a ponta). Você aprenderá como configurá-los, escrevê-los e executá-los corretamente nas plataformas Apple. No final — dicas práticas para organizar a arquitetura de testes.
Nível 1: Testes unitários — a base de tudo
Testes unitários verificam funções, métodos ou classes individuais de forma isolada. Em Swift, o framework principal é o XCTest. Ele está integrado ao Xcode, não requer dependências de terceiros e é ideal para testar a lógica de negócios.
Como escrever seu primeiro teste unitário
Crie um novo Unit Test Target no Xcode (File → New → Target → Unit Testing Bundle). Em seguida, adicione a classe de teste:
import XCTest@testable import MyApp
final class CalculatorTests: XCTestCase { var sut: Calculator! override func setUpWithError() throws { sut = Calculator() } override func tearDownWithError() throws { sut = nil } func testAddition() { // Given let a = 5 let b = 3 // When let result = sut.add(a, b) // Then XCTAssertEqual(result, 8, "A adição deve estar correta") }}Melhores práticas de teste unitário
- Isolamento: cada teste testa apenas uma unidade de código. Use
setUpetearDownpara redefinir o estado. - Nomenclatura: use o padrão
test[Method]_[Condition]_[ExpectedResult]. - Mocks e stubs: para dependências externas (rede, banco de dados), use protocolos e substitua-os nos testes.
- Cobertura: busque 70-80% de cobertura de código, mas não persiga 100% — teste os caminhos críticos.
Exemplo com mock
protocol NetworkServiceProtocol { func fetchData(completion: @escaping (Result<Data, Error>) -> Void)}
class MockNetworkService: NetworkServiceProtocol { var shouldReturnError = false func fetchData(completion: @escaping (Result<Data, Error>) -> Void) { if shouldReturnError { completion(.failure(NSError(domain: "test", code: 1))) } else { completion(.success(Data())) } }}
func testFetchDataSuccess() { let mock = MockNetworkService() let viewModel = ViewModel(networkService: mock) mock.shouldReturnError = false viewModel.loadData() XCTAssertTrue(viewModel.isDataLoaded)}Nível 2: Testes de integração — verificando a interação
Testes de integração verificam como diferentes módulos do sistema funcionam juntos: banco de dados + rede, UI + lógica de negócios, vários serviços. Em Swift, usa-se XCTest com dependências reais (ou in-memory) para isso.
Cenário típico: Core Data + Network
import XCTest@testable import MyApp
class UserServiceIntegrationTests: XCTestCase { var coreDataStack: CoreDataStack! var networkService: NetworkService! var userService: UserService! override func setUp() { coreDataStack = CoreDataStack(inMemory: true) // in-memory para testes networkService = NetworkService() userService = UserService(coreDataStack: coreDataStack, networkService: networkService) } func testSaveUserFromNetwork() { // Criamos uma expectativa para a requisição assíncrona let expectation = self.expe