Estrategias de prueba

Las pruebas automatizadas te ayudan a mejorar la calidad de la app de varias maneras. Por ejemplo, te ayuda a realizar validaciones, 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 utilizan un enfoque sistemático para las pruebas junto con mejoras en la infraestructura. De esta manera, se proporciona comentarios oportunos sobre el comportamiento del código. Una buena estrategia de pruebas hace lo siguiente:

  • Detecta los problemas lo antes posible.
  • Se ejecuta rápidamente.
  • Proporciona indicaciones claras cuando algo necesita reparación.

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 porción de 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 pruebas 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, en la que las pruebas pequeñas más numerosas forman la base y las pruebas grandes menos numerosas forman la punta.

Minimiza el costo de un error

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

Considera un ejemplo de una estrategia posiblemente ineficiente. 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:

Una estrategia con una gran cantidad de pruebas manuales y en la que las pruebas de dispositivos solo se ejecutan por la noche.
Figura 2: Una estrategia con una gran cantidad de pruebas manuales y en la que 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 tener en cuenta las implicaciones que esto tiene en el costo de identificar y corregir errores, y por qué es importante orientar tus esfuerzos de prueba hacia pruebas más pequeñas y frecuentes:

  • Cuando una prueba de unidades detecta un error, este se suele corregir 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 podría retrasar el lanzamiento. El costo de este error es más alto.

Dicho esto, una estrategia de prueba ineficiente es mejor que no tener ninguna. Cuando un error llega a producción, la corrección tarda mucho tiempo en llegar a los dispositivos de los usuarios, a veces semanas, por lo que el ciclo de retroalimentació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 pruebas de unidades, pruebas de componentes, pruebas de funciones, pruebas de aplicaciones y pruebas de versiones candidatas, en orden ascendente.
Figura 3: Una pirámide de pruebas de 5 capas.
  • Una prueba de unidades se ejecuta en la máquina host y verifica una sola unidad funcional de lógica sin dependencias en el framework de Android.
    • Ejemplo: Verificación de errores de "desviación en uno" en una función matemática.
  • Una prueba de componente verifica la funcionalidad o la apariencia de un módulo o componente de forma independiente de otros componentes del sistema. A diferencia de las pruebas de unidades, el área de superficie de una prueba de componentes se extiende a abstracciones más altas por encima de los métodos y las clases individuales.
  • Una prueba de funciones 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 archivo binario implementable. Son pruebas de integración grandes que usan un archivo binario depurable, como una compilación para desarrolladores que puede contener hooks de prueba, como el sistema en prueba.
    • Ejemplo: Prueba de comportamiento de la IU para verificar los cambios de configuración en un dispositivo plegable, pruebas de localización y accesibilidad
  • La prueba de versión candidata para lanzamiento verifica la funcionalidad de una compilación de lanzamiento. Son similares a las pruebas de aplicaciones, excepto que el archivo binario de la aplicación está reducido y optimizado. Estas son pruebas de integración de extremo a extremo a gran escala 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 ni a back-ends 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 pruebas 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

A nivel del módulo o del componente

Varias clases juntas

No

Local
Robolectric
Emulator

Depurable

Antes de la combinación

Función

Nivel de la función

Integración con componentes que pertenecen a otros equipos

Mocked

Local
Robolectric
Emulator
Devices

Depurable

Antes de la combinación

Aplicación

Nivel de aplicación

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


Servidor de etapa de pruebas
Servidor de producción

Emulador
Dispositivos

Depurable

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

Versión potencial

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

Post-merge
Pre-release

Decide la categoría de la prueba

Como regla general, debes considerar el nivel más bajo de la pirámide que puede brindarle al equipo el nivel adecuado de comentarios.

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 de la prueba

Descripción de lo que se está probando

Categoría de prueba

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

Apariencia 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 autorización

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

Pruebas de funciones

Prueba de JVM con objetos simulados

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 etapa 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, si algo pertenece a una categoría o a otra puede ser subjetivo. Puede haber motivos adicionales por los que una prueba se suba o baje, como el costo de la infraestructura, la inestabilidad y los tiempos de prueba prolongados.

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

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

Infraestructura de pruebas

La estrategia de pruebas debe contar con infraestructura y herramientas que ayuden a los desarrolladores a ejecutar sus pruebas de forma continua y a aplicar reglas que garanticen que todas las pruebas se aprueben.

Puedes categorizar las pruebas por alcance para definir cuándo y dónde ejecutar cada una. Por ejemplo, según 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 dispositivo plegable

Después de la combinación, ya sea que se haya combinado o enviado un cambio

Versión potencial

8 teléfonos diferentes, 1 plegable y 1 tablet

Antes del lanzamiento

  • Las pruebas de unidades y componentes se ejecutan en el sistema de integración continua para cada nueva confirmación, 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 versión candidata se ejecutan todas las noches en un teléfono, un dispositivo plegable y una tablet.
  • Antes del lanzamiento, se ejecutan pruebas de candidato a lanzamiento en una gran cantidad de dispositivos.

Estas reglas pueden cambiar con el tiempo cuando la cantidad de pruebas afecta la productividad. Por ejemplo, si mueves las pruebas a una cadencia nocturna, es posible que disminuyas los tiempos de compilación y prueba de la CI, pero también podrías prolongar el ciclo de retroalimentación.