Unit Test vs Integration Test Comparación

Velocidad, aislamiento y ciclo de feedback confiable

VS
Integration Test

Interacción real de componentes, validación de extremo a extremo

8 min de lecturaiOS

Comparación de puntuaciones

Cargando gráfico...

Puntuación detallada

Puntuación detallada: Unit Test y Integration Test — puntuaciones por categoría sobre 10
CategoríaUnit TestIntegration Test
Rendimiento
10/10
4/10
Facilidad de aprendizaje
8/10
6/10
Ecosistema
9/10
8/10
Comunidad
9/10
8/10
Mercado laboral
9/10
8/10
A prueba de futuro
9/10
9/10

Pros y contras

Unit Test

Pros

  • Extremadamente rápido — corre en milisegundos, en segundos en CI
  • Aislado — las dependencias externas se controlan con mock/stub
  • Confiable — riesgo mínimo de test flaky (sin red, sin BD)
  • Detección de regresiones — captura al instante pequeños cambios de código
  • Documentación — tests unitarios bien escritos muestran cómo usar el código
  • Mejora la calidad del diseño mediante TDD
  • Se puede ejecutar en paralelo — aprovecha múltiples núcleos eficientemente

Contras

  • Los mocks pueden no reflejar la realidad — riesgo de falsos positivos/negativos
  • No detecta problemas de integración — el escenario típico de 'los tests unitarios pasaron pero producción explotó'
  • Riesgo de over-mocking — los tests pueden acoplarse fuertemente a la implementación
  • Probar métodos privados puede requerir cambios de diseño
  • No demuestra la compatibilidad real entre módulos independientes entre sí

Ideal para

Validación de lógica de negocioTests de funciones puras y algoritmosTransiciones de estado de ViewModel/ReducerTests de parsers, formatters, validadoresDesarrollo de librerías y frameworks

Integration Test

Pros

  • Valida la interacción real de componentes — sin el sesgo de los mocks
  • Detecta errores de integración — prueba que las unidades funcionen juntas
  • Permite probar capas reales de base de datos y red
  • Valida flujos críticos de extremo a extremo
  • Da confianza en refactors — captura cambios de interfaz
  • Tests más cercanos a los escenarios reales de usuario

Contras

  • Lento — red, base de datos y sistema de archivos en tiempo real
  • Riesgo de tests flaky — los servicios externos pueden ser poco fiables
  • Complejidad de setup y teardown
  • Es difícil encontrar la causa de un test fallido
  • La ejecución en paralelo puede generar conflictos de estado
  • Requiere infraestructura adicional para entornos sin mock

Ideal para

Operaciones CRUD de base de datos (Core Data, SwiftData, SQLite)Validación de la capa de cliente APIFuncionamiento conjunto de varios serviciosValidación de rutas críticas (pago, flujo de autenticación)Tests de migración y transformación de datos

Comparación de código

Unit Test
// Swift - Ejemplos de tests unitarios (XCTest + Swift Testing)
import Testing
import Foundation
@testable import MyApp

// Framework Swift Testing (iOS 17+, WWDC 2024)
struct PriceFormatterTests {

    @Test("El formato de la lira turca debe ser correcto")
    func turkishLiraFormat() {
        let formatter = PriceFormatter(locale: Locale(identifier: "tr_TR"))
        #expect(formatter.format(1234.5) == "₺1.234,50")
        #expect(formatter.format(0) == "₺0,00")
        #expect(formatter.format(-50) == "-₺50,00")
    }

    @Test("Un precio inválido no debe ser negativo",
          arguments: [-1.0, -100.0, -0.01])
    func negativePriceValidation(price: Double) {
        let validator = PriceValidator()
        #expect(!validator.isValid(price))
    }
}

struct CartViewModelTests {
    var sut: CartViewModel!
    var mockRepository: MockCartRepository!

    @Test("El precio total debe actualizarse al añadir un producto")
    mutating func addProductUpdatesTotalPrice() async throws {
        mockRepository = MockCartRepository()
        sut = CartViewModel(repository: mockRepository)

        let product = Product(id: "p1", name: "MacBook", price: 75000)
        await sut.addProduct(product)

        #expect(sut.totalPrice == 75000)
        #expect(sut.itemCount == 1)
    }

    @Test("La cantidad debe aumentar al añadir el mismo producto dos veces")
    mutating func addSameProductIncreasesQuantity() async throws {
        mockRepository = MockCartRepository()
        sut = CartViewModel(repository: mockRepository)

        let product = Product(id: "p1", name: "MacBook", price: 75000)
        await sut.addProduct(product)
        await sut.addProduct(product)

        #expect(sut.items.count == 1)
        #expect(sut.items.first?.quantity == 2)
        #expect(sut.totalPrice == 150000)
    }
}

// Implementación mock
class MockCartRepository: CartRepositoryProtocol {
    var savedItems: [CartItem] = []

    func save(_ item: CartItem) async throws {
        savedItems.append(item)
    }

    func fetchAll() async throws -> [CartItem] {
        return savedItems
    }
}
Integration Test
// Swift - Ejemplos de tests de integración
import XCTest
@testable import MyApp

// Test de integración con SQLite en memoria
class UserRepositoryIntegrationTests: XCTestCase {
    var repository: UserRepository!
    var database: TestDatabase!

    override func setUp() async throws {
        // Usamos una base de datos SQLite real en memoria
        database = try await TestDatabase.inMemory()
        repository = UserRepository(database: database)
    }

    override func tearDown() async throws {
        try await database.cleanup()
        database = nil
        repository = nil
    }

    func testCreateAndFetchUser() async throws {
        // Crear
        let userId = try await repository.createUser(
            name: "Ahmet Yılmaz",
            email: "[email protected]"
        )

        // Obtener
        let fetchedUser = try await repository.fetchUser(id: userId)

        XCTAssertNotNil(fetchedUser)
        XCTAssertEqual(fetchedUser?.name, "Ahmet Yılmaz")
        XCTAssertEqual(fetchedUser?.email, "[email protected]")
    }

    func testDeleteUserCascadesToPosts() async throws {
        let userId = try await repository.createUser(name: "Test", email: "[email protected]")
        let postRepo = PostRepository(database: database)
        _ = try await postRepo.createPost(title: "Post 1", userId: userId)
        _ = try await postRepo.createPost(title: "Post 2", userId: userId)

        // Eliminar el usuario
        try await repository.deleteUser(id: userId)

        // Sus posts también deben eliminarse (cascade)
        let posts = try await postRepo.fetchPosts(userId: userId)
        XCTAssertTrue(posts.isEmpty, "Al eliminar el usuario, sus posts también deben eliminarse")
    }
}

// Test de integración de red con URLSession real
class APIClientIntegrationTests: XCTestCase {
    func testFetchPublicAPIData() async throws {
        // Este test usa una conexión de red real
        // En CI solo debe ejecutarse en entornos con conectividad
        try XCTSkipUnless(ProcessInfo.processInfo.environment["INTEGRATION_TESTS"] == "1")

        let client = APIClient(baseURL: URL(string: "https://jsonplaceholder.typicode.com")!)
        let posts: [Post] = try await client.request(path: "/posts")
        XCTAssertFalse(posts.isEmpty)
        XCTAssertEqual(posts.count, 100)
    }
}

Conclusión

Pirámide de testing: muchos tests unitarios (70%), pocos tests de integración (20%), un mínimo de tests UI/E2E (10%). Usa los tests unitarios para feedback rápido y los de integración para validar flujos críticos, de forma complementaria. La estrategia correcta no es sustituir uno por el otro, sino usarlos juntos.

Solicita una consultoría gratuita
FAQ

Preguntas frecuentes

No hay un número mágico. Un 80%+ de cobertura de la lógica de negocio es un buen objetivo. Pero lo importante no es el porcentaje de cobertura sino probar las cosas correctas. Perseguir ciegamente el 100% de cobertura puede ser contraproducente.

Artículos de blog relacionados

Ver todos los artículos

Proyectos relacionados

Ver todos los proyectos
Todas las comparaciones