← Back to blog

Pruebas en Apps iOS — Unit Tests

Una guía sobre cómo implementar tests unitarios en iOS usando TDD y XCTest.

La Testing Pyramid es un modelo que ayuda a estructurar las pruebas de software de manera eficiente. Los tests unitarios forman la capa más extensa de esta pirámide.

Tests Unitarios

Los tests unitarios son esenciales para verificar la lógica de tu aplicación, asegurando que dada una entrada de datos, el resultado sea constante. Presentan ventajas significativas:

  • La parte inferior de la pirámide es más rápida que la superior
  • Los unit tests son más fáciles de mantener
  • Requieren menos tiempo y esfuerzo para su mantenimiento

Desarrollo Guiado por Pruebas (TDD)

TDD se basa en iteraciones de "Red-Green-Refactor":

  1. Red: Escribir un test que falle
  2. Green: Añadir código necesario para que el test pase
  3. Refactor: Mejorar el código para que sea más limpio y eficiente

Ejemplo Práctico

Crear una función que determine si un usuario es mayor de edad basándose en su año de nacimiento.

Tests iniciales (fallarán):

func testLegalAge() {
    XCTAssertTrue(isLegalAge(year: 1990))
    XCTAssertTrue(isLegalAge(year: 2000))
    XCTAssertTrue(isLegalAge(year: 2005))
}

func testMinorAge() {
    XCTAssertFalse(isLegalAge(year: 2010))
    XCTAssertFalse(isLegalAge(year: 2015))
    XCTAssertFalse(isLegalAge(year: 2020))
}

func testAgeLimit() {
    XCTAssertTrue(isLegalAge(year: 2006))
    XCTAssertFalse(isLegalAge(year: 2007))
}

Primera implementación (Green):

func isLegalAge(year: Int) -> Bool {
    let age = 2024 - year
    return age >= 18
}

Implementación refactorizada:

private func calculateAge(year: Int) -> Int {
    let currentDate = Date()
    let calendar = Calendar.current
    let currentYear = calendar.component(.year, from: currentDate)
    return currentYear - year
}

func isLegalAge(year: Int) -> Bool {
    calculateAge(year: year) >= 18
}

Esta refactorización aborda dos problemas: elimina el año hardcodeado y respeta el principio de responsabilidad única (SRP) de SOLID.

Cobertura de Tests

La cobertura de tests es el porcentaje de código revisado por los tests. En Xcode se puede visualizar en el Report Navigator y activando Code Coverage en el Adjust Editor.

Las funciones no cubiertas por tests muestran una franja roja con un indicador "0". Los números representan el número de ejecuciones dentro de los tests.

Tests No Necesarios

No todos los tests tienen igual valor. Por ejemplo, testear funciones nativas de frameworks como .sorted() es innecesario, ya que su funcionamiento ya ha sido verificado por los creadores del framework.

let stringArray = ["Banana", "Apple", "Orange", "Mango", "Peach"]
let sortedArray = stringArray.sorted()

Si bien podrías escribir un test:

func testAlphabeticalSortedArray() {
    let stringArray = ["Banana", "Apple", "Orange", "Mango", "Peach"]
    let sortedArray = stringArray.sorted()
    XCTAssertEqual(sortedArray, ["Apple", "Banana", "Mango", "Orange", "Peach"])
}

Este test verifica funcionalidad del framework, no de tu código. Si esta función falla, no puedes arreglar su implementación interna. Por lo tanto, testear código externo que no controlas generalmente desperdicia recursos.

Sin embargo, al trabajar con frameworks como URLSession o CLLocationManager, es recomendable crear protocolos y componentes ficticios para testeos de integración.