Estrategias de prueba

Las pruebas automatizadas te ayudan a mejorar la calidad de la app de varias maneras. Por ejemplo, ayuda a realizar la validación, detectar regresiones y verificar la compatibilidad. Una buena estrategia de pruebas te permite aprovechar las pruebas automatizadas para enfocarte en un beneficio importante: la productividad del desarrollador.

Los equipos alcanzan niveles más altos de productividad cuando usan un enfoque sistemático para las pruebas junto con mejoras en la infraestructura. De esta manera, se proporcionan comentarios oportunos sobre el comportamiento del código. Una buena estrategia de prueba hace lo siguiente:

  • Detecta los problemas lo antes posible.
  • Se ejecuta con rapidez.
  • Proporciona indicaciones claras cuando se debe corregir algo.

Esta página te ayudará a decidir qué tipos de pruebas implementar, dónde ejecutarlas y con qué frecuencia.

La pirámide de pruebas

Puedes categorizar las pruebas en aplicaciones modernas por tamaño. Las pruebas pequeñas se enfocan solo en una pequeña parte del código, lo que las hace rápidas y confiables. Las pruebas grandes tienen un alcance amplio y requieren configuraciones más complejas que son difíciles de mantener. Sin embargo, las pruebas grandes tienen más fidelidad* y pueden descubrir muchos más problemas de una sola vez.

*Fidelidad se refiere a la similitud del entorno de ejecución de prueba con el entorno de producción.

Por lo general, la distribución de la cantidad de pruebas por alcance se visualiza en una pirámide.
Figura 1: Por lo general, la distribución de la cantidad de pruebas por alcance se visualiza en una pirámide.

La mayoría de las apps deben tener muchas pruebas pequeñas y relativamente pocas pruebas grandes. La distribución de las pruebas en cada categoría debe formar una pirámide, con más pruebas pequeñas que forman la base y las menos numerosas grandes que forman la punta.

Minimiza el costo de un error

Una buena estrategia de pruebas maximiza la productividad de los desarrolladores y, al mismo tiempo, minimiza el costo de encontrar errores.

Considera un ejemplo de una estrategia posiblemente ineficaz. Aquí, la cantidad de pruebas por tamaño no se organiza en una pirámide. Hay demasiadas pruebas de extremo a extremo grandes y muy pocas pruebas de IU de componentes:

Es una estrategia de alto rendimiento en la que muchas de las pruebas se realizan de forma manual y las pruebas de dispositivos solo se ejecutan por la noche.
Figura 2: Es una estrategia de alto rendimiento en la que muchas de las pruebas se realizan de forma manual y las pruebas de dispositivos solo se ejecutan por la noche.

Esto significa que se ejecutan muy pocas pruebas antes de la combinación. Si hay un error, es posible que las pruebas no lo detecten hasta que se ejecuten las pruebas de extremo a extremo nocturnas o semanales.

Es importante considerar las implicaciones que esto tiene para el costo de identificar y corregir errores, y por qué es importante sesgar tus esfuerzos de prueba hacia pruebas más pequeñas y frecuentes:

  • Cuando una prueba de unidades detecta el error, por lo general, se corrige en minutos, por lo que el costo es bajo.
  • Una prueba de extremo a extremo podría tardar días en descubrir el mismo error. Esto podría atraer a varios miembros del equipo, lo que reduciría la productividad general y, posiblemente, retrasaría un lanzamiento. El costo de este error es más alto.

Dicho esto, una estrategia de pruebas ineficiente es mejor que no tener ninguna. Cuando un error llega a producción, la corrección tarda mucho en llegar a los dispositivos del usuario, a veces semanas, por lo que el ciclo de reacción es el más largo y costoso.

Una estrategia de pruebas escalable

Tradicionalmente, la pirámide de pruebas se divide en 3 categorías:

  • Pruebas de unidades
  • Pruebas de integración
  • Pruebas de extremo a extremo.

Sin embargo, estos conceptos no tienen definiciones precisas, por lo que los equipos pueden definir sus categorías de manera diferente, por ejemplo, con 5 capas:

Una pirámide de pruebas de 5 capas con las categorías de pruebas de unidades, pruebas de componentes, pruebas de funciones, pruebas de aplicaciones y pruebas de lanzamiento candidatas, en orden ascendente.
Figura 3: Una pirámide de pruebas de 5 capas.
  • Una prueba de unidad se ejecuta en la máquina host y verifica una sola unidad funcional de lógica sin dependencias en el framework de Android.
    • Ejemplo: verificar errores por un paso en una función matemática.
  • Una prueba de componente verifica la funcionalidad o el aspecto de un módulo o componente de forma independiente de otros componentes del sistema. A diferencia de las pruebas de unidades, la superficie de una prueba de componentes se extiende a abstracciones más altas por sobre métodos y clases individuales.
  • Una prueba de función verifica la interacción de dos o más componentes o módulos independientes. Las pruebas de funciones son más grandes y complejas, y suelen operar a nivel de la función.
  • Una prueba de aplicación verifica la funcionalidad de toda la aplicación en forma de un objeto binario implementable. Son pruebas de integración grandes que usan un objeto binario depurable, como una compilación de desarrollo que puede contener hooks de prueba, como el sistema a prueba.
    • Ejemplo: Prueba de comportamiento de la IU para verificar los cambios de configuración en pruebas de accesibilidad, localización y plegables
  • Una prueba de versión candidata verifica la funcionalidad de una compilación de lanzamiento. Son similares a las pruebas de aplicaciones, excepto que el objeto binario de la aplicación está reducido y optimizado. Estas son pruebas de integración de extremo a extremo de gran tamaño que se ejecutan en un entorno lo más cercano posible a la producción sin exponer la app a cuentas de usuario públicas o backends públicos.

Esta categorización tiene en cuenta la fidelidad, el tiempo, el alcance y el nivel de aislamiento. Puedes tener diferentes tipos de pruebas en varias capas. Por ejemplo, la capa de prueba de la aplicación puede contener pruebas de comportamiento, capturas de pantalla y rendimiento.

Alcance

Acceso a la red

Ejecución

Tipo de compilación

Ciclo de vida

Unidad

Método o clase únicos con dependencias mínimas

No

Local

Depurable

Antes de la combinación

Componente

Nivel del módulo o componente

Varias clases juntas

No

Local
Robolectric
Emulador

Depurable

Antes de la combinación

Función

Nivel de componente

Integración con componentes que pertenecen a otros equipos

Simulación

Dispositivos

Emulator
Robolectric Local

Depurable

Fusión previa

Aplicación

Nivel de aplicación

Integración con funciones o servicios que son propiedad de otros equipos

Simulado
Servidor de pruebas
Servidor de producción

Emulador
Dispositivos

Depurable

Antes de la combinación
Después de la combinación

Versión candidata

Nivel de aplicación

Integración con funciones o servicios que pertenecen a otros equipos

Servidor de producción

Emulador
Dispositivos

Compilación de versión reducida

Después de la combinación
Versión preliminar

Decide la categoría de prueba

Como regla general, debes considerar la capa más baja de la pirámide que pueda brindar al equipo el nivel correcto de retroalimentación.

Por ejemplo, considera cómo probar la implementación de esta función: la IU de un flujo de acceso. Según lo que necesites probar, elegirás diferentes categorías:

Sujeto a prueba

Descripción de lo que se está probando

Categoría de pruebas

Ejemplo de tipo de prueba

Lógica del validador de formularios

Es una clase que valida la dirección de correo electrónico con una expresión regular y verifica que se haya ingresado el campo de contraseña. No tiene dependencias.

Pruebas de unidades

Prueba de unidades de JVM local

Comportamiento de la IU del formulario de acceso

Un formulario con un botón que solo se habilita cuando se valida el formulario

Pruebas de componentes

Prueba de comportamiento de la IU que se ejecuta en Robolectric

Aspecto de la IU del formulario de acceso

Un formulario que sigue una especificación de UX

Pruebas de componentes

Prueba de captura de pantalla de la vista previa de Compose

Integración con el administrador de autenticación

La IU que envía credenciales a un administrador de autenticación y recibe respuestas que pueden contener diferentes errores.

Pruebas de funciones

Prueba JVM con simulaciones

Diálogo de acceso

Una pantalla que muestra el formulario de acceso cuando se presiona el botón de acceso.

Pruebas de aplicaciones

Prueba de comportamiento de la IU que se ejecuta en Robolectric

Recorrido crítico del usuario: Acceso

Un flujo de acceso completo con una cuenta de prueba en un servidor de pruebas

Versión potencial

Prueba de comportamiento de la IU de Compose de extremo a extremo que se ejecuta en el dispositivo

En algunos casos, que algo pertenezca a una categoría o a otra puede ser subjetivo. Puede haber otros motivos por los que una prueba se mueve hacia arriba o hacia abajo, como el costo de la infraestructura, la inestabilidad y los tiempos de prueba largos.

Ten en cuenta que la categoría de prueba no determina el tipo de prueba y que no todas las funciones se deben probar en todas las categorías.

Las pruebas manuales también pueden ser parte de tu estrategia de pruebas. Por lo general, los equipos de QA realizan pruebas de lanzamiento candidatas, pero también pueden participar en otras etapas. Por ejemplo, pruebas exploratorias de errores en una función sin una secuencia de comandos.

Infraestructura de pruebas

Una estrategia de pruebas debe estar respaldada por infraestructura y herramientas para ayudar a los desarrolladores a ejecutar continuamente sus pruebas y aplicar reglas que garanticen que todas las pruebas se aprueban.

Puedes categorizar las pruebas por alcance para definir cuándo y dónde ejecutarlas. Por ejemplo, siguiendo el modelo de 5 capas:

Categoría

Entorno (dónde)

Activador (cuándo)

Unidad

[Local][4]

Cada confirmación

Componente

Local

Cada confirmación

Función

Local y emuladores

Antes de la combinación, antes de combinar o enviar un cambio

Aplicación

Local, emuladores, 1 teléfono y 1 plegable

Después de la combinación, después de combinar o enviar un cambio

Versión candidata

8 teléfonos diferentes, 1 dispositivo plegable y 1 tablet

Antes del lanzamiento

  • Las pruebas de unidad y componente se ejecutan en el sistema de integración continua para cada confirmación nueva, pero solo para los módulos afectados.
  • Todas las pruebas de unidad, componente y función se ejecutan antes de combinar o enviar un cambio.
  • Las pruebas de aplicación se ejecutan después de la combinación.
  • Las pruebas de versiones candidatas se ejecutan todas las noches en un teléfono, un dispositivo plegable y una tablet.
  • Antes de una versión, las pruebas de Versión candidata se ejecutan en una gran cantidad de dispositivos.

Estas reglas pueden cambiar con el tiempo cuando la cantidad de pruebas afecta la productividad. Por ejemplo, si pasas las pruebas a una cadencia nocturna, puedes disminuir los tiempos de compilación y prueba de CI, pero también puedes prolongar el ciclo de comentarios.