Pruebas unitarias en desarrollo móvil: qué son, métodos y frameworks

Autor: IT Sectr Publicado: 2026-04-06 Tiempo de lectura: 9 min

Las pruebas unitarias son un método de verificación de software en el que se prueba la corrección de módulos individuales o funciones de código de forma aislada del resto del sistema. Según Martin Fowler, 2026, las pruebas unitarias son la base de CI/CD y la refactorización, proporcionando retroalimentación rápida sobre el funcionamiento del código. Las pruebas modulares ayudan a detectar errores en las primeras etapas del desarrollo, reduciendo el costo de corregirlos decenas de veces.

Puntos clave

  • Prueba unitaria — verificación de un solo módulo (función, método, clase) de forma aislada de las dependencias externas
  • Principios FIRST — Fast, Isolated, Repeatable, Self-validating, Timely — la base de las pruebas de calidad
  • Mocks y stubs — sustitutos de dependencias externas (BD, API, sistema de archivos) que garantizan el aislamiento de la prueba
  • TDD (Test-Driven Development) — metodología de desarrollo guiado por pruebas: rojo-verde-refactorización
  • Pirámide de pruebas — las pruebas unitarias ocupan el 70% de la pirámide, proporcionando retroalimentación rápida en cada commit

¿Qué son las pruebas unitarias?

Las pruebas unitarias son el proceso de verificar unidades individuales del código fuente (funciones, métodos, clases) de forma aislada del resto del programa. Cada prueba ejecuta un escenario de uso específico del módulo y comprueba que el resultado coincide con el esperado. Las pruebas unitarias se escriben en el mismo lenguaje de programación que el código principal y se ejecutan automáticamente en el entorno de desarrollo o en un pipeline de CI/CD. A diferencia de las pruebas de integración, las pruebas unitarias no interactúan con bases de datos reales, sistemas de archivos o servicios de red.

¿Por qué se necesitan las pruebas unitarias?

El objetivo principal es la retroalimentación rápida sobre la corrección del código después de los cambios. Si un desarrollador refactoriza un método, el conjunto de pruebas unitarias confirma que el comportamiento no se ha roto. Según Google Testing Blog (2025), los proyectos con cobertura de pruebas unitarias superior al 60% tienen 2.5 veces menos incidentes en producción. Beneficios adicionales: documentación del código (las pruebas muestran cómo usar la API), simplificación de la refactorización (se puede cambiar la implementación manteniendo el comportamiento) y diagnóstico rápido de regresiones.

¿Qué se considera una prueba unitaria?

No toda prueba automatizada es una prueba unitaria. Criterios: se prueba un solo módulo (clase o función), las dependencias externas se reemplazan con mocks o stubs, la prueba se ejecuta en milisegundos y no requiere iniciar un servidor o base de datos. Una prueba que accede a una base de datos real es una prueba de integración. Una prueba que abre un navegador es una prueba E2E. Comprender los límites entre los tipos de pruebas es importante para distribuir correctamente los esfuerzos en la pirámide de pruebas.

Principios FIRST y estructura AAA

Las pruebas unitarias de calidad siguen los principios FIRST formulados por Robert C. Martin. Cada prueba debe ser Fast (rápida — milisegundos), Isolated (aislada — no depende de otras pruebas), Repeatable (reproducible — mismo resultado en cualquier máquina), Self-validating (autovalidable — resultado "passed" o "failed", sin verificación manual) y Timely (oportuna — escrita antes o simultáneamente con el código). Violar cualquier principio reduce el valor de la prueba.

Estructura AAA (Arrange-Act-Assert)

Una plantilla estándar para escribir pruebas unitarias. Arrange — preparación de datos y dependencias: crear objetos, configurar mocks, establecer parámetros de entrada. Act — ejecución de la acción probada: llamar a un método o función. Assert — verificación del resultado: comparar el valor real con el esperado. La división en tres bloques hace que la prueba sea legible y comprensible. Si el bloque Assert requiere lógica compleja, es probable que la prueba esté verificando demasiado a la vez.

kotlin
// Ejemplo de prueba unitaria con patrón AAA en Kotlin con JUnit 5
class CalculatorTest {

    private lateinit var calculator: Calculator

    @BeforeEach
    fun setUp() {
        // ARRANGE — creamos el objeto a probar
        calculator = Calculator()
    }

    @Test
    fun addition_shouldReturnCorrectSum() {
        // ACT — ejecutamos la acción
        val result = calculator.add(2, 3)

        // ASSERT — verificamos el resultado
        Assertions.assertEquals(5, result)
    }
}

Nombrado de pruebas

El nombre de la prueba debe describir qué se verifica y qué resultado se espera. Formato: [methodName]_[scenario]_[expectedResult]. Ejemplo: calculateTotal_whenDiscountApplied_shouldReturnDiscountedTotal. Un buen nombre de prueba reemplaza un comentario y, al fallar, indica inmediatamente qué funcionalidad está dañada. Evite nombres como test1, checkSomething o verify — no aportan información y dificultan el diagnóstico.

Mocks, stubs y fakes: qué y cuándo usar

Para aislar el módulo probado de las dependencias externas se utilizan dobles de prueba (test doubles). Tipos principales: mocks — verifican que un método específico fue llamado con los parámetros esperados; stubs — devuelven valores predefinidos al llamar a un método; fakes — implementaciones simplificadas de componentes reales (por ejemplo, InMemoryUserRepository en lugar de UserRepository que trabaja con una BD). La elección depende de lo que se necesita verificar: estado (stub) o interacción (mock).

DobleQué verificaEjemplo
MockLlamada al método con parámetros correctosuserRepository.save(user) fue llamado exactamente 1 vez
StubValor de retornorepository.findById(1) devuelve User(id=1, name="Test")
FakeLógica mediante implementación simplificadaInMemoryMapUserRepository con HashMap en lugar de BD
SpyMocking parcial de un objeto realspy(repo).when(findById).thenReturn(user)

Mockito: ejemplo de mocking en Java/Kotlin

Mockito es el framework de mocking más popular para Java y Kotlin. Permite crear mocks mediante mock(), configurar valores de retorno mediante when().thenReturn() y verificar llamadas mediante verify(). Las versiones modernas de Mockito (5.x) admiten mocks estáticos (mockStatic) y sintaxis simplificada a través de BDDMockito (given-willReturn). Una regla importante: no mockees lo que no es tuyo — no crees mocks para objetos de valor y bibliotecas estándar.

kotlin
// Ejemplo de prueba unitaria con Mockito en Kotlin
class OrderServiceTest {

    @Mock
    private lateinit var paymentGateway: PaymentGateway

    @Mock
    private lateinit var userRepository: UserRepository

    private lateinit var orderService: OrderService

    @BeforeEach
    fun init() {
        MockitoAnnotations.openMocks(this)
        orderService = OrderService(paymentGateway, userRepository)
    }

    @Test
    fun processOrder_whenPaymentFails_shouldThrowException() {
        // given
        val user = User(id = 1, balance = 100.0)
        val order = Order(amount = 200.0)
        Mockito.`when`(paymentGateway.charge(any())).thenReturn(false)
        Mockito.`when`(userRepository.findById(1)).thenReturn(user)

        // when & then
        assert Throws<PaymentException> {
            orderService.processOrder(user.id, order)
        }

        // verify
        Mockito.verify(paymentGateway).charge(any())
    }
}

TDD: desarrollo guiado por pruebas

TDD (Test-Driven Development) es una metodología en la que la prueba se escribe antes de la implementación del código. El ciclo "Red-Green-Refactor": escribir una prueba que falla (Red), escribir el código mínimo para pasar la prueba (Green), mejorar el código sin cambiar el comportamiento (Refactor). TDD garantiza que todo el código esté cubierto por pruebas (cobertura = 100% para la funcionalidad implementada) y que el código sea comprobable — si el código es difícil de probar, la arquitectura necesita mejorar.

Beneficios de TDD

Según una investigación de IBM (2006-2026, estudio longitudinal), los equipos que usan TDD tienen entre un 40 y un 80% menos de defectos en producción en comparación con los equipos que escriben pruebas después del código. TDD también mejora la arquitectura: el desarrollador se ve obligado a pensar en el diseño de la API antes de la implementación, lo que conduce a un acoplamiento débil (loose coupling) y una alta cohesión (high cohesion). Un efecto adicional es la documentación con código vivo: las pruebas sirven como especificación del comportamiento del módulo, que siempre está actualizada.

¿Cuándo no es adecuado TDD?

TDD no siempre es óptimo. Los componentes de UI son difíciles de probar de forma aislada — para ellos son más efectivas las pruebas snapshot o las pruebas de regresión visual (Percy, Chromatic). La creación de prototipos y la investigación (spike solutions) no requieren pruebas. El código legacy sin pruebas es difícil de cubrir mediante TDD — aquí primero se necesitan characterization tests (pruebas que capturan el comportamiento actual antes de la refactorización). En estos casos, TDD no se abandona por completo sino que se adapta — se escriben pruebas para la funcionalidad modificada, no para todo el código legacy.

Pruebas unitarias en aplicaciones móviles

El desarrollo móvil tiene sus particularidades: la lógica de negocio a menudo se mezcla con el código de UI (Activity, ViewController, ViewModel), lo que dificulta las pruebas unitarias. La mejor práctica es Vistas delgadas, ViewModels gruesos: extraer toda la lógica de los componentes de UI a clases separadas (UseCase, Repository, ViewModel) que sean fáciles de probar sin emulador. Android e iOS tienen frameworks nativos de pruebas unitarias que se ejecutan en JVM/Native sin necesidad de iniciar un dispositivo.

Pruebas unitarias en Android (JUnit + Mockito/Robolectric)

Las pruebas unitarias de Android se ejecutan en una JVM local sin emulador, lo que proporciona velocidad de ejecución — una prueba típica tarda menos de 100 ms. JUnit 5 es el runner principal. Para pruebas de ViewModel, use kotlinx-coroutines-test para probar corrutinas y Turbine para probar StateFlow. Robolectric permite probar componentes dependientes de Android (Context, Resources) sin emulador, cargando shadow-classes. Para pruebas de Compose, use Compose UI Test — pero estas son pruebas de UI, no unitarias.

Pruebas unitarias en iOS (XCTest + Quick/Nimble)

Las pruebas unitarias de iOS se escriben en Swift con XCTest (integrado en Xcode). Quick + Nimble son frameworks BDD para pruebas más legibles (describe/context/it). Para mocking, use Cuckoo (generación de mocks) o SwiftyMocky. Swift admite protocolos e inyección de dependencias, lo que facilita el reemplazo de dependencias. Punto clave: las pruebas unitarias de iOS se ejecutan en el simulador de macOS, no en un dispositivo real. Las pruebas que requieren funciones de hardware (cámara, Bluetooth) son pruebas de integración.

Pruebas unitarias en Flutter (flutter_test + Mockito)

Las pruebas unitarias de Flutter usan el paquete flutter_test y se ejecutan en Dart VM sin emulador. Para mocking, use el paquete mockito con generación de código (build_runner). Las pruebas de widgets (en el mismo paquete) prueban widgets individuales pero requieren renderizado y son más lentas — úselas solo para verificar la lógica de UI. La lógica pura de Dart (modelos, repositorios, blocs) se prueba como pruebas Dart regulares sin importar flutter_test.

dart
// Ejemplo de prueba unitaria en Flutter con mockito
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';

@GenerateMocks([ApiClient])
import 'user_repository_test.mocks.dart';

void main() {
    late MockApiClient mockApi;
    late UserRepository repository;

    setUp(() {
        mockApi = MockApiClient();
        repository = UserRepository(mockApi);
    });

    test('fetchUser returns user when API succeeds', () async {
        // Arrange
        final expectedUser = User(id: 1, name: 'Test');
        when(mockApi.getUser(1))
            .thenAnswer((_) async => expectedUser);

        // Act
        final result = await repository.fetchUser(1);

        // Assert
        expect(result, expectedUser);
        verify(mockApi.getUser(1)).called(1);
    });
}

Mejores prácticas y errores comunes

Las pruebas unitarias efectivas requieren disciplina. La regla principal: pruebe el comportamiento, no la implementación. La prueba no debe saber cómo está implementado el módulo internamente (qué métodos privados se llaman, en qué orden). Si una prueba está vinculada a la implementación, se rompe en cada refactorización y pierde valor. La prueba verifica el contrato: con entrada X, la salida debe ser Y. La excepción son las pruebas para algoritmos con rendimiento crítico, donde la secuencia de llamadas es importante.

  • Una verificación por prueba — un assert o un grupo de asserts relacionados para una verificación lógica
  • Evite la duplicación — use @BeforeEach / setUp para la inicialización común, pruebas parametrizadas para diferentes datos de entrada
  • No pruebe métodos privados — pruebe a través de la API pública. Si un método privado no está cubierto, su lógica no es visible externamente
  • Cubra casos límite — colecciones vacías, null/undefined, números negativos, valores máximos
  • No use Thread.sleep en las pruebas — hace que las pruebas sean lentas e inestables. Use timeouts de prueba y corrutinas

¿Qué cobertura se considera suficiente?

La cobertura del 100% es un objetivo inalcanzable e innecesario. Según Google Testing Blog (2025), el nivel óptimo de cobertura para pruebas unitarias es del 70-80% de las líneas de código. La cobertura del 100% a menudo se logra probando getters, setters y constructores, lo que no aporta valor. Concéntrese en la lógica de negocio crítica: cálculos complejos, validación, manejo de errores, edge-cases. Use JaCoCo (Java), Coverage.py (Python), Istanbul (JS) para la medición y configure un umbral en CI — fallo de compilación con cobertura inferior al 60%.

CI/CD y pruebas unitarias

Las pruebas unitarias son la primera etapa de cualquier pipeline de CI/CD. Se ejecutan en cada push al repositorio, antes de la compilación y el despliegue. El tiempo promedio de ejecución del conjunto de pruebas unitarias no debe exceder los 5 minutos — si es mayor, las pruebas dejan de ser "rápidas" y los desarrolladores dejan de ejecutarlas localmente. Separe las pruebas en rápidas (unitarias) y lentas (integración) y ejecútelas en diferentes etapas del pipeline. Use ejecución en paralelo y fail-fast para acelerar.

Preguntas frecuentes

¿En qué se diferencia una prueba unitaria de una de integración?

Una prueba unitaria verifica un solo módulo de forma aislada, reemplazando las dependencias externas con mocks. Una prueba de integración verifica la interacción entre varios componentes reales (BD, API, sistema de archivos). Las pruebas unitarias se ejecutan en milisegundos, las de integración en segundos. En la pirámide de pruebas, las unitarias ocupan el 70%.

¿Qué framework elegir para pruebas unitarias?

La elección depende de la plataforma: JUnit 5 para Java/Kotlin, XCTest para iOS/Swift, pytest para Python, Jest/Vitest para JavaScript/TypeScript, flutter_test para Flutter. Para mocking, use Mockito (Java), Cuckoo (iOS), unittest.mock (Python) o vitest.mock (JS). Todos los frameworks modernos admiten pruebas parametrizadas, assertions integradas y ejecución en paralelo.

¿Cuáles son los principios F.I.R.S.T. de pruebas?

Fast — la prueba se ejecuta en milisegundos. Isolated — no depende de otras pruebas ni sistemas externos. Repeatable — da el mismo resultado en cualquier máquina. Self-validating — verifica el resultado automáticamente. Timely — escrita antes o sincrónicamente con el código. Violar aunque sea un principio reduce la efectividad de las pruebas.

¿Es necesario escribir pruebas unitarias para ViewModel en Android/iOS?

Sí, absolutamente. El ViewModel contiene lógica de negocio — manejo de eventos, transformación de datos, gestión de estado. En Android, use kotlinx-coroutines-test para corrutinas y Turbine para probar StateFlow. En iOS, pruebe Combine Publishers o async/await en ViewModel. Las pruebas de ViewModel son pruebas unitarias puras que se ejecutan en JVM/macOS sin emulador.

¿Cómo probar código con solicitudes de red?

Las solicitudes de red en pruebas unitarias no se ejecutan — se reemplazan con mocks del cliente HTTP. En Android, use MockWebServer (OkHttp) — inicia un servidor HTTP local, que es preferible a los mocks porque reproduce la interacción real de red. MockWebServer proporciona aislamiento sin perder realismo. Para iOS — OHHTTPStubs o URLProtocol para interceptar y reemplazar respuestas.

Resumen

  • Pruebas unitarias — verificación de módulos individuales de forma aislada de las dependencias externas con retroalimentación rápida
  • Estructura AAA — Arrange (preparación), Act (acción), Assert (verificación) — plantilla estándar de pruebas
  • Mocks y stubs — dobles de prueba para aislamiento: los mocks verifican llamadas, los stubs devuelven valores
  • TDD — desarrollo guiado por pruebas (Red-Green-Refactor) reduce la cantidad de defectos en un 40-80%
  • Principios FIRST — Fast, Isolated, Repeatable, Self-validating, Timely — base de las pruebas de calidad
  • Herramientas por plataforma — JUnit 5 (Android), XCTest (iOS), flutter_test (Flutter), Jest (React Native)
  • Cobertura 70-80% — nivel óptimo para la lógica de negocio crítica, getters y setters no requieren pruebas

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