Introducción: Por qué las pruebas son la base de una aplicación Swift confiable
El desarrollo para iOS y macOS requiere no solo una interfaz atractiva, sino también una lógica estable. Las pruebas en Swift no son una opción, sino una necesidad. Sin pruebas, incluso un cambio pequeño puede romper funcionalidades que han funcionado durante años. En este artículo analizaremos tres niveles de pruebas: unit (pruebas unitarias), integration (pruebas de integración) y e2e (pruebas de extremo a extremo). Aprenderá cómo configurarlas, escribirlas y ejecutarlas correctamente en las plataformas de Apple. Al final, encontrará consejos prácticos para organizar la arquitectura de pruebas.
Nivel 1: Pruebas unitarias — la base de todo
Las pruebas unitarias verifican funciones, métodos o clases individuales de forma aislada. En Swift, el framework principal es XCTest. Está integrado en Xcode, no requiere dependencias externas y es ideal para probar la lógica de negocio.
Cómo escribir tu primera prueba unitaria
Cree un nuevo Unit Test Target en Xcode (File → New → Target → Unit Testing Bundle). Luego agregue la clase de prueba:
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, "La suma debe ser correcta")
}
}
Mejores prácticas de pruebas unitarias
- Aislamiento: cada prueba verifica solo una unidad de código. Use
setUpytearDownpara restablecer el estado. - Nomenclatura: use el patrón
test[Method]_[Condition]_[ExpectedResult]. - Mocks y stubs: para dependencias externas (red, base de datos), use protocolos y sustitúyalos en las pruebas.
- Cobertura: busque un 70-80% de cobertura de código, pero no persiga el 100% — pruebe las rutas críticas.
Ejemplo con 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)
}
Nivel 2: Pruebas de integración — verificación de la interacción
Las pruebas de integración verifican cómo diferentes módulos del sistema trabajan juntos: base de datos + red, UI + lógica de negocio, múltiples servicios. En Swift, se usa XCTest con dependencias reales (o en memoria).
Escenario 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) // en memoria para pruebas
networkService = NetworkService()
userService = UserService(coreDataStack: coreDataStack, networkService: networkService)
}
func testSaveUserFromNetwork() {
// Creamos una expectativa para la solicitud asíncrona
let expectation = self.expe