Si llevas tiempo inmerso en la programación Swift, es muy probable que tu relación con XCTest haya tenido altibajos. Aunque ha sido la herramienta por defecto durante años, la rápida evolución del ecosistema hacia paradigmas más declarativos —impulsada por Swift y SwiftUI— hacía necesaria una renovación en la forma en que verificamos nuestro código.
Apple ha respondido con Swift Testing, un framework moderno, de código abierto y diseñado específicamente para aprovechar la potencia actual del lenguaje Swift.
En este tutorial técnico en profundidad, exploraremos qué es Swift Testing, cómo configurarlo en Xcode y cómo transformar tu flujo de trabajo de calidad en aplicaciones iOS, macOS y watchOS. Si eres un iOS developer que busca modernizar su stack tecnológico y escribir pruebas más claras y potentes, este artículo es para ti.
¿Qué es Swift Testing y por qué representa el futuro?
Swift Testing es la nueva generación de herramientas de pruebas automatizadas en el ecosistema Apple. A diferencia de XCTest, que arrastra una arquitectura heredada de los tiempos de Objective-C, Swift Testing se ha construido sobre las características más modernas de Swift, destacando el uso intensivo de Macros y la concurrencia estructurada.
Las diferencias clave frente a XCTest
Para un iOS developer, el cambio de mentalidad se centra en la expresividad y la seguridad:
- Sintaxis basada en Macros: Nos despedimos de la verbosidad de
XCTAssertEqualoXCTAssertTrue. Ahora utilizamos expresiones naturales con#expecty#require. - Tests Descriptivos: Los tests pueden tener nombres legibles por humanos y metadatos detallados sin depender de la nomenclatura de la función (
func testNombreLargo()). - Concurrencia Nativa: Funciona armónicamente con
async/await, eliminando la necesidad de losXCTestExpectationen la gran mayoría de escenarios. - Tests Parametrizados: Ejecutar una misma lógica de prueba con múltiples sets de datos es ahora una funcionalidad nativa y extremadamente sencilla de implementar.
- Multiplataforma: Está diseñado para ser consistente en todas las plataformas donde corre Swift (incluyendo Linux y Windows), no solo en los dispositivos Apple.
Setup: Configuración del Entorno en Xcode
Integrar Swift Testing en tu flujo de trabajo es directo, ya que las versiones modernas de Xcode lo soportan de forma nativa. La gran ventaja es que este framework puede convivir pacíficamente con XCTest en el mismo proyecto, permitiéndote una migración gradual sin fricción.
Paso 1: Configurar el Target de Pruebas
Para un proyecto nuevo:
- Ve a File > New > Project.
- Al configurar las opciones del proyecto, asegúrate de marcar la casilla Include Tests.
- Xcode configurará automáticamente el sistema de pruebas predeterminado (que en las versiones más recientes ya prioriza Swift Testing).
Para un proyecto existente:
- Navega al editor de targets de tu proyecto.
- Añade un nuevo Unit Testing Bundle pulsando el botón
+. - Verifica que el
Deployment Targetde tu proyecto sea compatible con las características modernas de Swift (generalmente iOS 16+, macOS 13+, watchOS 9+ para aprovechar todas las capacidades de las macros, aunque el framework soporta versiones anteriores mediante back-deployment).
Paso 2: Importar el Framework
En tu archivo de pruebas, la importación es limpia y directa. Olvida import XCTest.
import Testing
@testable import MiAppIncreible
struct MisPrimerosTests {
// Aquí definiremos las pruebas
}Nota de Arquitectura: En Swift Testing, se recomienda utilizar
struct(estructuras) oactorspara definir tus suites de pruebas, en lugar declass. Al usarstruct, garantizas que cada prueba se ejecute en su propia instancia aislada, evitando los temidos errores por “estado compartido” (shared state) que a menudo ocurren en clases mal gestionadas.
Anatomía de un Test en Swift Testing
Vamos a diseccionar la sintaxis. El objetivo de Apple con este framework es que el código de prueba sea tan legible como la documentación.
La Macro @Test
Para convertir una función en una prueba ejecutable, simplemente la anotas con @Test. El nombre de la función ya no necesita prefijos obligatorios.
@Test("Verificar que el cálculo del IVA es correcto")
func calculoIVA() {
let precio = 100.0
let iva = precio * 0.21
#expect(iva == 21.0)
}El string que pasas como argumento a la macro es lo que verás en el “Test Navigator” de Xcode. Esto es vital para documentar la intención de tu código, una práctica esencial en la programación Swift de alto nivel.
Aserciones: #expect vs #require
El framework simplifica las docenas de aserciones antiguas en dos macros potentes:
#expect(condicion): Evalúa una expresión booleana. Si la condición es falsa, el test se marca como fallido, pero la ejecución del código continúa. Es el equivalente directo a las aserciones tradicionales.
#expect(usuario.nombre == "Juan")
#expect(items.count > 0)Lo mágico aquí es que, si falla, Xcode expandirá la macro para mostrarte exactamente los valores de las variables en el momento del fallo, sin necesidad de escribir mensajes de error personalizados.
try #require(condicion): Evalúa una expresión. Si falla, el test se detiene inmediatamente lanzando un error. Esto es crucial cuando necesitas desempaquetar un opcional (Optional Unwrapping) para continuar con la prueba.
let usuario = try #require(servicio.obtenerUsuario(id: 1))
// Si la línea anterior falla, el test aborta aquí.
// Esto evita crashes en la siguiente línea:
#expect(usuario.esActivo)Tests Parametrizados: Potencia con Menos Código
Para un ios developer, una de las tareas más tediosas es escribir múltiples tests para validar la misma función con diferentes inputs. Swift Testing soluciona esto de raíz.
Imagina que estás desarrollando una app en SwiftUI y necesitas validar un formulario de contraseñas.
struct ValidadorPassword {
static func esValida(_ pass: String) -> Bool {
return pass.count >= 8
}
}
// Test Parametrizado
@Test("Validación de longitud de contraseña", arguments: [
("corta", false),
("12345678", true),
("contraseñaMuyLargaSegura", true),
("", false)
])
func validarLongitud(password: String, resultadoEsperado: Bool) {
let esValida = ValidadorPassword.esValida(password)
#expect(esValida == resultadoEsperado)
}Con este único bloque de código, Xcode generará y ejecutará 4 pruebas distintas. En el reporte de resultados, verás cada caso individualmente. Si falla el caso de la cadena vacía, sabrás exactamente cuál fue, manteniendo el resto de validaciones intactas.
Organización Profesional: Traits y Tags
En proyectos complejos, ejecutar la suite completa de tests puede llevar tiempo. Swift Testing introduce un sistema de Tags (etiquetas) flexible y tipado.
Creando Tags Personalizados
Primero, extiendes la estructura Tag en tu código de test:
import Testing
extension Tag {
@Tag static var critico: Self
@Tag static var ui: Self
@Tag static var integracion: Self
}Aplicando Tags a Tests o Suites
Puedes etiquetar una función individual o agrupar toda una estructura (Suite) bajo una etiqueta.
@Suite("Tests de Integración de Red")
@Tag(.integracion)
struct TestsAPI {
@Test("Login Exitoso", .tags(.critico))
func login() async throws {
// Lógica de test asíncrono
}
}Esto te permite filtrar la ejecución en Xcode (o en CI/CD) para correr, por ejemplo, solo los tests .critico antes de un commit rápido.
Swift Testing en un entorno SwiftUI (MVVM)
Para aterrizar estos conceptos, veamos un ejemplo real de cómo testear un ViewModel en una app moderna creada con Swift y SwiftUI.
El Código (ViewModel)
import SwiftUI
@MainActor
class ListaTareasViewModel: ObservableObject {
@Published var tareas: [String] = []
func agregar(tarea: String) {
guard !tarea.isEmpty else { return }
tareas.append(tarea)
}
func borrarTodo() {
tareas.removeAll()
}
}El Test con Swift Testing
Aquí aprovecharemos la concurrencia. Dado que el ViewModel está marcado con @MainActor (obligatorio para actualizaciones de UI en SwiftUI), nuestro test debe respetar ese aislamiento.
import Testing
@testable import MiAppTareas
@Suite("Gestión de Tareas ViewModel")
struct TareasTests {
@Test("Agregar tarea válida aumenta el contador")
func agregarTarea() async {
// Arrange (Preparar)
let viewModel = await ListaTareasViewModel()
// Act (Actuar)
await viewModel.agregar(tarea: "Aprender Swift Testing")
// Assert (Verificar)
let count = await viewModel.tareas.count
#expect(count == 1)
let primeraTarea = await viewModel.tareas.first
#expect(primeraTarea == "Aprender Swift Testing")
}
@Test("Agregar tarea vacía no hace nada")
func agregarTareaVacia() async {
let viewModel = await ListaTareasViewModel()
await viewModel.agregar(tarea: "")
let count = await viewModel.tareas.count
#expect(count == 0)
}
}Observa la limpieza del código. El manejo de async es natural. No hay que lidiar con expectativas (expectations), ni wait, ni callbacks complejos. El framework gestiona la espera de las funciones asíncronas automáticamente.
Migración y Coexistencia: ¿Qué pasa con XCTest?
Una pregunta común es: “¿Debo reescribir todos mis tests antiguos?”. La respuesta rotunda es no.
Swift Testing y XCTest están diseñados para coexistir. Xcode detecta ambos tipos de pruebas en tu proyecto y los ejecuta en la misma sesión.
- Utiliza Swift Testing para todo el código nuevo y nuevas funcionalidades.
- Mantén tus tests antiguos en XCTest hasta que necesites refactorizar esa parte del código.
- Ten en cuenta que, por el momento, XCTest sigue siendo necesario para UI Testing (pruebas de interfaz de usuario automatizadas) y pruebas de rendimiento (
measure), áreas donde Swift Testing se integra o delega.
Errores Comunes y Mejores Prácticas
Al adoptar esta nueva herramienta en tu día a día de programación Swift, considera estos consejos:
- Evita el Force Unwrap (!): Nunca uses
!en tus tests. Si el valor es nulo, provocará un crash que detendrá toda la suite de pruebas. Siempre usatry #require(valor). - Preferencia por Structs: Como mencionamos, usa
structpara definir tus suites. Si usasclass, corres el riesgo de modificar una propiedad en elTest Aque afecte el resultado delTest Bsi no limpias correctamente el estado. Las estructuras eliminan este problema por diseño. - Tests Atómicos: Cada
@Testdebe probar una sola cosa. Gracias a los tests parametrizados, ya no hay excusa para agrupar muchas aserciones dispares en una sola función.
Conclusión
Swift Testing no es simplemente un cambio sintáctico; es una evolución necesaria en la madurez del desarrollo para plataformas Apple. Para el iOS developer, representa una herramienta más segura, rápida y alineada con la filosofía moderna del lenguaje.
Al integrar este framework en tus proyectos de Swift y SwiftUI, descubrirás que escribir tests deja de ser una tarea tediosa para convertirse en una parte fluida y valiosa del desarrollo. La capacidad de usar macros inteligentes, la concurrencia simplificada y la facilidad de los tests parametrizados te permitirán elevar la calidad de tus apps con menos esfuerzo.
Si tienes cualquier duda sobre este artículo, contacta conmigo y estaré encantado de ayudarte 🙂. Puedes contactar conmigo en mi perfil de X o en mi perfil de Instagram










