Introducción: por qué las pruebas son la base de una aplicación Swift confiable
\n\nEl desarrollo para iOS y macOS requiere una alta confiabilidad del código. Un error en la lógica de funcionamiento de la aplicación puede costar la experiencia del usuario y, en proyectos comerciales, dinero. Las pruebas en Swift no son solo una "buena práctica", sino una etapa obligatoria del desarrollo. Permiten detectar errores en etapas tempranas, simplificar la refactorización y mejorar la calidad del código.
\n\nEn este artículo analizaremos tres niveles de pruebas: unit (pruebas unitarias), integration (pruebas de integración) y e2e (pruebas de extremo a extremo). Aprenderá en qué se diferencian, cuándo aplicar cada una y verá ejemplos prácticos en Swift utilizando el framework XCTest.
1. Pruebas unitarias en Swift: la base fundamental
\n\nLas pruebas unitarias verifican módulos individuales (funciones, métodos, clases) de forma aislada. Su objetivo principal es asegurarse de que cada "unidad" de código funcione correctamente.
\n\nConfiguración de XCTest
\n\nXCTest está integrado en Xcode. Para crear un target de prueba:
\n- \n
- Abra su proyecto en Xcode. \n
- Seleccione File → New → Target. \n
- Seleccione Unit Testing Bundle. \n
Después de la creación, obtendrá un archivo como YourAppTests.swift.
Ejemplo de prueba unitaria
\n\nSupongamos que tenemos una función para calcular un descuento:
\n\nfunc calculateDiscount(price: Double, discount: Double) -> Double {\n guard discount >= 0 && discount <= 100 else { return price }\n return price * (1 - discount / 100)\n}\n\n\nEscribimos la prueba:
\n\nimport XCTest\n@testable import YourApp\n\nclass DiscountTests: XCTestCase {\n func testCalculateDiscount_ValidInput() {\n let result = calculateDiscount(price: 1000, discount: 20)\n XCTAssertEqual(result, 800, accuracy: 0.01)\n }\n \n func testCalculateDiscount_InvalidDiscount() {\n let result = calculateDiscount(price: 500, discount: 150)\n XCTAssertEqual(result, 500)\n }\n}\n\n\nBest practices para pruebas unitarias:
\n- \n
- Una prueba, una verificación. \n
- Use
setUp()para inicializar objetos comunes. \n - Evite dependencias de red, base de datos o UI — use mocks. \n
2. Pruebas de integración: verificación de la interacción entre componentes
\n\nLas pruebas de integración verifican cómo diferentes módulos trabajan juntos. Por ejemplo, la interacción del modelo de datos con la capa de red o la base de datos CoreData.
\n\nEjemplo: prueba de una solicitud de red
\n\nCreemos un servicio simple:
\n\nclass NetworkService {\n func fetchUser(completion: @escaping (Result<User, Error>) -> Void) {\n // Solicitud real a la API\n }\n}\n\n\nUna prueba de integración puede verificar que el servicio procesa correctamente la respuesta de un servidor real (o de un servidor mock local):
\n\nclass NetworkServiceIntegrationTests: XCTestCase {\n var service: NetworkService!\n \n override func setUp() {\n super.setUp()\n service = NetworkService()\n }\n \n func testFetchUser_ReturnsUser() {\n let expectation = self.expectation(description: "Fetch user")\n \n service.fetchUser { result in\n switch result {\n case .success(let user):\n XCTAssertNotNil(user.name)\n case .failure:\n XCTFail("Request failed")\n }\n expectation.fulfill()\n }\n \n waitForExpectations(timeout: 5)\n }\n}\n\n\nImportante: Las pruebas de integración son más lentas que las pruebas unitarias, por lo que se ejecutan con menos frecuencia (por ejemplo, antes de fusionar en main).
\n\n3. Pruebas E2E (de extremo a extremo) con XCUITest
\n\nLas pruebas E2E (End-to-End) simulan las acciones del usuario: presionar botones, ingresar texto, verificar la visualización de la UI. En Swift para esto