Instructivos

Alternativas a los recursos inactivos en las pruebas de Compose: las APIs de waitUntil (actualizadas)

Lectura de 3 min
Jose Alcérreca
Ingeniera de Relaciones con Desarrolladores

En este artículo, aprenderás a usar la API de prueba waitUntil en Compose para esperar a que se cumplan ciertas condiciones. En algunas situaciones, esta es una buena alternativa al uso de recursos inactivos.

[Actualización de 2023] En resumen: Usa las nuevas APIs de waitUntil para sincronizarte en las pruebas de Compose (v1.4.0 y versiones posteriores).


¿Qué es la sincronización?

Una forma de categorizar las pruebas es por su alcance. Las pruebas pequeñas, o pruebas de unidades, se enfocan en pequeñas partes de tu app, mientras que las pruebas grandes, o de extremo a extremo, abarcan una gran parte de tu app. Puedes leer sobre este y otros tipos de pruebas en la documentación de pruebas recientemente actualizada.

Presiona Intro o haz clic para ver la imagen en tamaño original

large_0_9n_Nqkt_HHUTOQ_In_AI_b113b43bcf.png
Diferentes alcances de prueba en una app

La sincronización es el mecanismo que le permite a la prueba saber cuándo ejecutar la siguiente operación. Cuanto más grande sea el fragmento de código que elijas verificar, más difícil será sincronizarlo con la prueba. En las pruebas de unidades, es fácil tener el control total de la ejecución del código para verificarlo. Sin embargo, a medida que aumentamos el alcance para incluir más clases, módulos y capas, se vuelve difícil para el framework de pruebas saber si la app está en medio de una operación o no.

Presiona Intro o haz clic para ver la imagen en tamaño original

large_correct_b1a355f41b.webp
Sincronización correcta entre la prueba y la app

androidx.test y, por extensión, Compose Test, usan algunos trucos internos para que no tengas que preocuparte demasiado por esto. Por ejemplo, si el subproceso principal está ocupado, la prueba se detiene hasta que pueda ejecutar la siguiente línea.

Sin embargo, no pueden saberlo todo. Por ejemplo, si cargas datos en un subproceso en segundo plano, es posible que el framework de pruebas ejecute la siguiente operación demasiado pronto, lo que hará que falle la prueba. La peor situación es cuando esto sucede solo un pequeño porcentaje del tiempo, lo que hace que la prueba sea inestable.

Opción 1: Recursos inactivos

Los recursos inactivos son una función de Espresso que te permite, como desarrollador, decidir cuándo la app está ocupada. Tienes dos formas de usarlos:

1. Instalarlos en el framework o la biblioteca que realiza un trabajo que la prueba no puede ver

Un buen ejemplo de esto es RxIdler, que encapsula un programador de RxJava. Esta es la forma preferida de registrar recursos inactivos, ya que te permite mantener la configuración de la prueba claramente separada del código de prueba.

2. Modificar el código en prueba para exponer de forma explícita información sobre si tu app está ocupada o no

Por ejemplo, podrías modificar tu repositorio (o un doble de prueba) para indicar que está ocupado mientras carga datos de una fuente de datos:

Esto no es ideal porque contaminas tu código de producción o creas dobles de prueba complicados, y hay algunas situaciones en las que son difíciles de instalar. Por ejemplo, ¿cómo usarías los recursos de inactividad en un flujo de Kotlin? ¿Cuál es la actualización final?

En cambio, podemos esperar a que sucedan las cosas.

Opción 2: Espera de la manera incorrecta

La carga de datos suele ser rápida, en especial cuando se usan datos falsos, por lo que no tiene sentido perder tiempo con recursos inactivos cuando puedes hacer que la prueba espere unos segundos.

Esta prueba se ejecutará más lento de lo necesario o fallará. Cuando tienes cientos o miles de pruebas de IU, quieres que las pruebas sean lo más rápidas posible.

Además, a veces los emuladores o los dispositivos se comportan de forma incorrecta y se traban, lo que hace que esa operación tarde un poco más de esos 2,000 ms y, por lo tanto, se interrumpa la compilación. Cuando tienes cientos de pruebas, esto se convierte en un gran problema.

0_DOCdjq-JpPDGV5OB.png

Opción 3: Espera de la manera correcta

Si no quieres modificar el código que se está probando para exponer cuándo está ocupado, otra opción es esperar hasta que se cumpla una condición determinada, en lugar de esperar una cantidad de tiempo arbitraria.

1_jIYFxE4qlHXMi2SwW6JemA.png

En Compose, puedes aprovechar la función waitUntil, que toma otra función que produce un valor booleano.

Actualización del 22/3/2023: A partir de Compose 1.4.0, agregamos un nuevo conjunto de APIs de waitUntil:

[Antes de la versión 1.4.0: Usa estos asistentes: waitUntilExists, waitUntilNodeCount]

… y úsalos de la siguiente manera:

Usa estas APIs solo cuando necesites sincronizar tu prueba con la IU. La sincronización en cada instrucción de prueba contamina el código de prueba de forma innecesaria, lo que dificulta su mantenimiento.

¿Cuándo deberías usarlo? Un buen caso de uso es cargar datos desde un observable (con LiveData, Kotlin Flow o RxJava). Cuando tu IU necesita recibir varias actualizaciones antes de que la consideres inactiva, es posible que desees simplificar la sincronización con waitUntil.

Por ejemplo, cuando recopilas un flujo de una vista, sucede lo siguiente:

Y emites varios elementos a él:

Si repository tarda una cantidad indeterminada de tiempo en devolver el primer resultado, el framework de prueba considerará que “Cargando” es el estado inactivo (el valor inicial asignado en collectAsState) y continuará con la siguiente sentencia.

Por lo tanto, puedes hacer que la prueba sea mucho más confiable si te aseguras de que la IU no muestre el indicador de carga:


¡Felices… espera… pruebas!


Licencia de fragmentos de código:

Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0
Escrito por:

Seguir leyendo