Comprender los distintos niveles de pruebas en Android marca la diferencia entre una aplicación frágil y una verdaderamente robusta. Desde los tests de instrumentación con Espresso hasta las pruebas end-to-end con Appium, cada capa de la pirámide de testing cumple un rol específico que protege la calidad del software y garantiza que los cambios futuros no rompan funcionalidades existentes.
¿Qué son los tests de instrumentación y por qué usan Espresso?
Los tests de instrumentación validan toda la parte visual de la aplicación [0:08]. A diferencia de los tests unitarios, aquí se utiliza Espresso, el framework que provee Android para acceder a las vistas, darles clic y validar su comportamiento. Con Espresso es posible realizar acciones como scroll, swipe up, swipe down y scroll horizontal, simulando lo que un usuario real haría con la app.
Estas pruebas requieren un emulador para ejecutarse [0:40], ya que necesitan acceso al framework de Android: botones, pantallas, RecyclerView y todos los elementos visuales. Por esta razón, la velocidad de ejecución es media, dado que depende del sistema operativo, del framework y del consumo de memoria que genera la automatización. Aun así, sigue siendo más rápido que lo que haría una persona manualmente.
¿Cómo funcionan las pruebas end-to-end con Appium?
Las pruebas end-to-end validan flujos completos del usuario [1:20]: un login, una compra, agregar ítems al carrito o registrar una tarjeta de crédito. Para esto existe Appium, un framework que permite escribir estas pruebas incluso fuera del ecosistema Android.
- Appium actúa como un robot que da clics en coordenadas específicas de la pantalla.
- Los scripts pueden escribirse en Python, Kotlin o Java.
- Son multiplataforma: un mismo script puede probar Android e iOS [2:10].
- Normalmente las gestiona un equipo de automation con su propio repositorio y servidor.
La velocidad de ejecución es la más lenta porque valida flujos enteros y requiere mayor fuerza de procesamiento.
¿Qué papel cumple el manual testing en la pirámide?
En la punta de la pirámide se encuentra el manual testing [2:50], reservado para lo que aún no se puede automatizar: animaciones y transiciones. Estas son evaluaciones subjetivas que dependen de la perspectiva humana, por lo que requieren validación manual.
¿Cómo crear una prueba unitaria del patrón repositorio?
El ejemplo práctico parte del patrón repositorio desarrollado en clases anteriores [3:15]. Para generar la prueba, se hace clic derecho sobre la implementación del repositorio, se selecciona Generate y luego Test, eligiendo JUnit 5.
Android Studio organiza las pruebas en dos paquetes distintos:
- androidTest: para tests de instrumentación.
- test: para tests unitarios [3:55].
El test se genera en la carpeta test como CourseRepositoryImplementationTest. Las dependencias del repositorio son la base de datos y la fuente remota. Aquí entra un concepto fundamental: las dependencias mock [4:40].
Como el repositorio recibe interfaces en su constructor (no implementaciones concretas), es posible crear clases mock con comportamiento personalizado. Por ejemplo:
DatabaseDependencyMock retorna una lista con cuatro cursos predefinidos.
RemoteDependencyMock retorna un ArrayList vacío.
La anotación @Before indica a JUnit que ejecute el método setUp antes de cada prueba [4:20], donde se inicializan estas dependencias y se crea la instancia del repositorio.
¿Cómo aplicar la estructura Given-When-Then con Gherkin?
Cada test sigue la estructura Gherkin con tres partes [6:30]:
- Given: precondiciones (en este caso ya configuradas en el setUp).
- When: el evento, que es llamar al método
getCourses del repositorio.
- Then: la validación del resultado esperado.
El caso de prueba simula obtener cursos sin conexión. Como el remote dependency devuelve una lista vacía, la lógica del repositorio debe recurrir a la base de datos. Por lo tanto, el assert correcto es que la lista no sea vacía [8:00], no que sea vacía.
Si alguien modifica la lógica del repositorio incorrectamente, la prueba fallará, brindando la garantía de que el comportamiento esperado se mantiene.
¿Por qué es peligroso usar assert true en todas las pruebas?
Una experiencia real ilustra un error común [9:30]: un equipo tenía excelentes reportes de Coverage (porcentaje de código testeado), pero todas las validaciones usaban assertTrue, lo que hacía que las pruebas siempre pasaran sin importar los cambios en el código.
- Las pruebas deben validar el comportamiento real esperado.
- Usar assert genéricos anula el propósito del testing.
- Librerías como Mockito y MockK facilitan la creación de mocks más sofisticados [9:15].
El verdadero valor de las pruebas radica en blindar la aplicación para que sea escalable, mantenible y robusta. ¿Qué pasa si hay conexión pero el servidor responde con un error 500? ¿Y si no hay conexión ni datos locales? Todos estos escenarios deben programarse como tests independientes.
¿Ya implementas pruebas en tus proyectos Android? Comparte tu experiencia y cuéntanos qué tipo de test te ha resultado más útil.