XCTest: conceptos clave, clases XCTestCase y escritura de pruebas

Autor: IT Sectr Publicado: 2026-04-08 Tiempo de lectura: 11 min

XCTest es un framework de Apple para pruebas unitarias y de integración de aplicaciones para iOS, macOS, watchOS y tvOS. XCTest forma parte de Xcode y admite la escritura de pruebas en Swift y Objective-C. A diferencia de frameworks de terceros (Quick, Nimble), XCTest es la solución oficial de Apple y está completamente integrado con Xcode Server y CI/CD. Según Apple Developer (2024), XCTest se utiliza en el 94% de las aplicaciones iOS del top 100 de App Store. XCTest proporciona una base estable para escribir pruebas unitarias y pruebas de UI sin dependencias externas.

Puntos clave

  • XCTest — el framework oficial de Apple para pruebas unitarias y pruebas de UI en Swift y Objective-C.
  • XCTestCase — la clase base para todas las pruebas, que proporciona setUp, tearDown y métodos de aserción.
  • Aserciones — XCTAssertTrue, XCTAssertEqual, XCTAssertNil y otras para verificar resultados esperados.
  • XCTestExpectation — un mecanismo para probar código asíncrono con espera de cumplimiento.
  • Pruebas de rendimiento — medición del tiempo de ejecución del código mediante el método measure(metrics:) con umbrales.

¿Qué es XCTest?

XCTest es un framework para pruebas unitarias, de integración y de UI desarrollado por Apple e integrado en Xcode desde la versión 5.0 (2013). XCTest reemplazó a OCUnit (SenTestingKit) y proporcionó una API moderna en Swift con soporte para pruebas asíncronas, pruebas de rendimiento e integración con Xcode Server. Según Swift.org (2024), XCTest es la base para las pruebas en todos los proyectos de Apple, incluido Swift Package Manager, que utiliza XCTest para la autovalidación.

XCTest funciona junto con Xcode Test Navigator y Report Navigator, que muestran el árbol de pruebas, el historial de ejecuciones y comparan resultados entre builds. Test Navigator permite ejecutar una sola prueba, un grupo de pruebas o todo el conjunto sin modificar código. Los resultados se muestran con iconos verdes (aprobadas), rojos (fallidas) y amarillos (omitidas). Según Apple WWDC (2024), Xcode 16 mejoró la ejecución paralela de pruebas en un 40% mediante el uso de múltiples simuladores.

XCTest es compatible con las plataformas: iOS 8.0+, macOS 10.10+, watchOS 2.0+, tvOS 9.0+. Cada plataforma tiene la misma API, lo que permite escribir pruebas multiplataforma. Swift Testing — un nuevo framework de Apple (anunciado en 2024) — complementará a XCTest en el futuro pero no lo reemplazará por completo. XCTest sigue siendo el framework principal de pruebas en el ecosistema Apple.

XCTestCase — la clase base para pruebas

XCTestCase es la clase base de la que heredan todas las clases de prueba en XCTest. Proporciona el ciclo de vida de la prueba: `setUp()` se llama antes de cada prueba, `tearDown()` después de cada prueba. setUp se usa para inicializar objetos y mocks, tearDown para limpiar recursos. setUpWithError y tearDownWithError permiten manejar errores de inicialización sin try-catch en cada prueba.

Cada método cuyo nombre comienza con `test` es reconocido automáticamente por Xcode como una prueba. Alternativamente, se puede usar la macro `@Test` (Swift Testing). Los nombres de las pruebas deben ser descriptivos: `testLoginWithValidCredentials` es mejor que `testLogin1`. Documentar las pruebas mediante comentarios es una buena práctica, pero Xcode también permite agregar descripciones a través de User-Defined Attributes.

swift
import XCTest

class UserServiceTests: XCTestCase {

    var sut: UserService!
    var mockSession: MockURLSession!

    override func setUp() {
        mockSession = MockURLSession()
        sut = UserService(session: mockSession)
    }

    override func tearDown() {
        sut = nil
        mockSession = nil
    }

    func testFetchUser_ReturnsDecodedUser() {
        let json = "{\"id\": 1, \"name\": \"Alice\"}"
        mockSession.setResponse(json)
        let user = try await sut.fetchUser(id: 1)
        XCTAssertEqual(user.name, "Alice")
    }
}

El ejemplo anterior muestra la estructura estándar de XCTestCase. sut (System Under Test) es una convención de nomenclatura para el objeto que se está probando. MockURLSession reemplaza la red real, lo que permite probar UserService de forma aislada. El principio de “una prueba — una aserción” simplifica la depuración: si una prueba falla, el desarrollador sabe inmediatamente qué funcionalidad está rota. Cada prueba de XCTestCase debe verificar un escenario o una aserción.

Aserciones en XCTest

Grupo básico de aserciones

XCTAssertTrue y XCTAssertFalse son aserciones básicas para verificar valores booleanos. XCTAssertTrue(expression) pasa si expression == true. XCTAssertEqual verifica la igualdad de dos valores con soporte para todos los tipos que implementan Equatable. Para números de punto flotante, se usa XCTAssertEqual con el parámetro accuracy para tener en cuenta la precisión del cálculo. Según Google Testing Blog (2024), XCTAssertEqual cubre el 70% de todas las comprobaciones en un conjunto de pruebas típico.

Aserciones nil y errores

XCTAssertNil y XCTAssertNotNil verifican valores opcionales para nil. Estas aserciones son críticas en Swift, donde los tipos opcionales se usan ampliamente. XCTAssertThrowsError verifica que el código lanza un error esperado. XCTUnwrap es una aserción que desempaqueta un valor opcional y falla con un mensaje claro si el valor es nil. La comparación de cadenas mediante XCTAssertEqual utiliza comparación literal, no semántica. XCTAssertNoThrow es la aserción complementaria para verificar que el código no lanza un error.

AserciónPropósitoEjemplo
XCTAssertEqualVerificación de igualdadXCTAssertEqual(a, b)
XCTAssertTrueVerificación de verdadXCTAssertTrue(result)
XCTAssertNilVerificación de nilXCTAssertNil(error)
XCTAssertThrowsErrorVerificación de errorXCTAssertThrowsError(try parse(""))
XCTUnwrapDesempaquetado optionalXCTUnwrap(value)

XCTestExpectation y pruebas asíncronas

XCTestExpectation es un mecanismo para probar código asíncrono. La prueba crea una expectativa con un nombre descriptivo, la pasa a una operación asíncrona y llama a `wait(for:timeout:)`. Si la expectativa no se cumple dentro del tiempo de espera, la prueba falla. Tiempo de espera por defecto es de 10 segundos, pero para operaciones rápidas se recomienda establecer de 1 a 3 segundos para acelerar el tiempo total de prueba.

XCTWaiter es una alternativa más flexible a wait(for:timeout:). XCTWaiter permite esperar múltiples expectativas, configurar el orden de ejecución y manejar tiempos de espera mediante programación. A diferencia de wait, XCTWaiter devuelve `XCTWaiter.Result`, que puede analizarse. Delegate XCTWaiterDelegate notifica sobre violaciones del orden de expectativas y tiempos de espera.

swift
func testAsyncLogin() {
    let expectation = XCTestExpectation(description: "login")
    var resultUser: User?

    sut.login(email: "a@b.com", password: "123") { user in
        resultUser = user
        expectation.fulfill()
    }

    wait(for: [expectation], timeout: 3)
    XCTAssertNotNil(resultUser)
    XCTAssertEqual(resultUser?.email, "a@b.com")
}

En el ejemplo, XCTestExpectation se usa para probar un inicio de sesión asíncrono. fulfill() se llama dentro del closure del callback, indicando que la operación asíncrona ha terminado. Si fulfill() no se llama en 3 segundos, la prueba falla con un tiempo de espera. Después de la espera exitosa, se realizan aserciones para verificar el resultado. Múltiples expectativas se pueden pasar como un array y esperar a que todas se cumplan.

Pruebas de rendimiento con XCTest

measure(metrics:) es un método de XCTestCase para crear pruebas de rendimiento. El bloque de código dentro de measure se ejecuta 10 veces consecutivas, y XCTest recopila estadísticas: tiempo promedio, mediana, desviación estándar. Metrics es un array de métricas rastreadas: XCTClockMetric (tiempo), XCTMemoryMetric (memoria), XCTStorageMetric (disco) y XCTCPUMetric (procesador). Según Apple WWDC (2024), las pruebas de rendimiento con XCTCPUMetric son útiles para detectar regresiones en algoritmos.

Baseline (línea base) para pruebas de rendimiento se establece en Xcode Test Plan. Si el tiempo de ejecución supera la línea base en un porcentaje establecido (por defecto 10%), la prueba se considera fallida. La línea base se actualiza manualmente después de confirmar que el cambio de rendimiento es esperado. Test Plan en Xcode permite agrupar pruebas de rendimiento por configuraciones: debug/release, diferentes dispositivos, diferentes versiones de iOS.

swift
func testArraySortPerformance() {
    let numbers = (1...10000).shuffled()
    measure(metrics: [XCTClockMetric()]) {
        let _ = numbers.sorted()
    }
}

Esta prueba de rendimiento mide el tiempo de ordenación de un array de 10.000 elementos. XCTClockMetric captura el tiempo de ejecución real. Si después de cambiar el algoritmo de ordenación el tiempo aumenta en un 10% o más, la prueba indicará una regresión. Las pruebas de rendimiento de XCTest son especialmente útiles para: algoritmos de procesamiento de datos, renderizado de componentes de UI, operaciones de base de datos y solicitudes de red.

Organización de pruebas e integración CI/CD

Estructura del proyecto de pruebas

La estructura del proyecto de pruebas en XCTest sigue la convención: un archivo de prueba por clase, colocado en un directorio separado `<TargetName>Tests`. Los nombres de archivo corresponden a los nombres de las clases probadas con el sufijo `Tests`: `UserService.swift` → `UserServiceTests.swift`. Los Test Targets en Xcode se configuran por separado para pruebas unitarias y pruebas de UI, lo que permite ejecutarlos de forma independiente. Schemes en Xcode gestionan la configuración de compilación y el conjunto de pruebas a ejecutar.

Integración CI/CD

Xcode Cloud y GitHub Actions admiten la ejecución de XCTest mediante `xcodebuild test -scheme App -testPlan SmokeTest`. El pipeline de CI incluye: compilación → ejecución de pruebas unitarias → ejecución de pruebas de UI → publicación del informe. El informe JUnit se genera mediante `xcodebuild` con la opción `-resultBundlePath` y se puede importar en cualquier herramienta de CI. Code Coverage es una función integrada de XCTest que muestra qué líneas de código están cubiertas por las pruebas. El umbral mínimo de cobertura para código de producción es del 70% para la lógica de negocio crítica.

Xcode Cloud y GitHub Actions admiten la ejecución de XCTest mediante `xcodebuild test -scheme App -testPlan SmokeTest`. El pipeline de CI incluye: compilación → ejecución de pruebas unitarias → ejecución de pruebas de UI → publicación del informe. Informe JUnit se genera mediante `xcodebuild` con la opción `-resultBundlePath` y se puede importar en cualquier herramienta de CI. Bitrise y Jenkins tienen pasos preparados para XCTest.

Code Coverage es una función integrada de XCTest que muestra qué líneas de código están cubiertas por las pruebas. Xcode muestra la cobertura en verde (cubierta), rojo (no cubierta) y amarillo (parcialmente cubierta). Umbral mínimo de cobertura para código de producción es del 70% para la lógica de negocio crítica. Según Google Testing Blog (2024), exigir un 80% de cobertura para todos los módulos genera “pruebas vacías” que no verifican la lógica, solo ejecutan código.

Preguntas frecuentes

¿En qué se diferencia XCTest de Quick y Nimble?

XCTest es el framework oficial de Apple con integración directa en Xcode. Quick y Nimble son bibliotecas de terceros que proporcionan sintaxis BDD y aserciones más legibles. Quick y Nimble son convenientes para Acceptance Testing, pero XCTest es más fiable para pruebas unitarias debido a la ausencia de dependencias externas.

¿Cómo probar código asíncrono en XCTest?

El código asíncrono se prueba mediante XCTestExpectation + `wait(for:timeout:)` o mediante métodos `async/await` de XCTest (iOS 13+). Para APIs basadas en callbacks, se crea una expectativa que se cumple en el closure. Para async/await, se usan aserciones estándar en funciones async.

¿Cómo simular dependencias en XCTest?

XCTest no incluye un framework de mocking integrado. Mocking se implementa mediante protocolos: se crea una clase mock que implementa el mismo protocolo que la dependencia real. Para la generación automática de mocks se usan Cuckoo, SwiftyMocky o mocks manuales. La inyección de dependencias mediante inicializadores es una condición obligatoria para la testabilidad.

¿Se puede ejecutar XCTest en dispositivos reales?

Sí, XCTest se ejecuta en dispositivos reales mediante Xcode o xcodebuild con el parámetro `-destination 'platform=iOS,name=iPhone 15'`. Las pruebas de UI en dispositivos reales dan resultados más precisos que en simuladores. Para ejecutar en granjas de dispositivos se usan BrowserStack, Sauce Labs o Firebase Test Lab.

¿Qué hay de nuevo en Swift Testing comparado con XCTest?

Swift Testing (2024) es un nuevo framework de Apple con macros `@Test`, `@Suite` y `@Expect`. Proporciona parametrización de pruebas integrada, agrupación en suites y una sintaxis más legible. Swift Testing coexiste con XCTest y no lo reemplaza. XCTest sigue siendo el framework principal para pruebas de UI y pruebas de rendimiento.

Resumen

  • XCTest — el framework oficial de pruebas de Apple, integrado en Xcode y compatible con Swift y Objective-C.
  • XCTestCase proporciona el ciclo de vida setUp/tearDown y un conjunto de aserciones para pruebas unitarias.
  • XCTestExpectation y XCTWaiter son mecanismos para probar código asíncrono con callbacks y async/await.
  • Pruebas de rendimiento usan measure(metrics:) con soporte para XCTClockMetric, XCTMemoryMetric y XCTCPUMetric.
  • Aserciones — XCTAssertEqual, XCTAssertTrue, XCTAssertNil, XCTAssertThrowsError, XCTUnwrap y otras.
  • Integración CI/CD mediante xcodebuild, Xcode Cloud y GitHub Actions con generación automática de informes.
  • Swift Testing — un nuevo framework de Apple que complementa a XCTest para pruebas unitarias y parametrización.

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también