Recursos inactivos de Espresso

Un recurso inactivo representa una operación asincrónica cuyos resultados afectan las operaciones posteriores en una prueba de IU. Cuando registras recursos inactivos con Espresso, puedes validar estas operaciones asincrónicas de manera más confiable que cuando pruebas la app.

Cómo identificar si se necesitan recursos inactivos

Espresso ofrece un sofisticado conjunto de funciones de sincronización. Sin embargo, esta característica del framework solo se aplica a las operaciones que publican mensajes en MessageQueue, como una subclase de View que dibuja su contenido en la pantalla.

Debido a que Espresso no tiene conocimiento de otras operaciones asincrónicas, incluidas las que se ejecutan en un subproceso en segundo plano, Espresso no puede proporcionar sus garantías de sincronización en esas situaciones. Para que Espresso esté al tanto de las operaciones de larga duración de tu app, debes registrar cada una de ellas como un recurso inactivo.

Si no usas los recursos inactivos cuando pruebas los resultados del trabajo asincrónico de la app, es posible que debas usar una de las siguientes soluciones alternativas para mejorar la confiabilidad de las pruebas:

  • Agregar llamadas a Thread.sleep(). Cuando agregas retrasos artificiales a las pruebas, el conjunto de pruebas tarda más en terminar de ejecutarse, y las pruebas pueden fallar a veces cuando se ejecutan en dispositivos más lentos. Además, estas demoras no se adaptan bien en las nuevas versiones, ya que es posible que tu app deba realizar un trabajo asíncrono más lento en versiones futuras.
  • Implementa wrappers de reintento, que usan un bucle para comprobar repetidamente si la app aún está realizando un trabajo asincrónico hasta que se agota el tiempo de espera. Incluso si especificas un recuento máximo de reintentos en las pruebas, cada reejecución consumirá recursos del sistema, en especial de la CPU.
  • Usar instancias de CountDownLatch, que permitan que una o más subunidades esperen hasta que se complete un número específico de operaciones que se están ejecutando en otra conversación. Estos objetos requieren que especifiques una duración de tiempo de espera; de lo contrario, la app podría bloquearse indefinidamente. Los bloqueos también agregan complejidad innecesaria al código, lo que dificulta el mantenimiento.

Espresso permite quitar estas soluciones poco confiables de tus pruebas y, en su lugar, te permite registrar el trabajo asincrónico de tu app como recursos inactivos.

Casos de uso comunes

Si realizas operaciones similares a las de los siguientes ejemplos en tus pruebas, procura usar un recurso inactivo:

  • Carga datos desde Internet o una fuente de datos local.
  • Establece conexiones con bases de datos y devoluciones de llamadas.
  • Administra servicios, ya sea mediante un servicio del sistema o una instancia de IntentService.
  • Desarrolla una lógica empresarial compleja, como transformaciones de mapas de bits.

Es muy importante registrar los recursos inactivos cuando estas operaciones actualizan una IU que luego tus pruebas validarán.

Ejemplo de implementaciones de recursos inactivos

En la siguiente lista, se describen varios ejemplos de implementaciones de recursos inactivos que se pueden integrar en tu app:

CountingIdlingResource
Realiza un recuento de tareas activas. Cuando el recuento es cero, el recurso asociado se considera inactivo. Esta funcionalidad es muy similar a la de un Semaphore. En la mayoría de los casos, esta implementación es suficiente para administrar el trabajo asincrónico de la app durante las pruebas.
UriIdlingResource
Es similar a CountingIdlingResource, pero el recuento debe ser cero durante un período específico antes de que el recurso se considere inactivo. Este período de espera adicional tiene en cuenta las solicitudes de red consecutivas, donde una app del subproceso puede realizar una nueva solicitud inmediatamente después de recibir una respuesta a una solicitud anterior.
IdlingThreadPoolExecutor
Es una implementación personalizada de ThreadPoolExecutor que realiza un seguimiento de la cantidad total de tareas en ejecución dentro de los grupos de subprocesos creados. Esta clase usa un CountingIdlingResource para mantener el recuento de tareas activas.
IdlingScheduledThreadPoolExecutor
Es una implementación personalizada de ScheduledThreadPoolExecutor. Proporciona la misma funcionalidad y capacidades que la clase IdlingThreadPoolExecutor, pero también puede realizar un seguimiento de las tareas programadas para el futuro o programadas para ejecutarse periódicamente.

Crea tu propio recurso inactivo

Como usas recursos inactivos en las pruebas de tu app, es posible que debas proporcionar la administración o el registro de recursos personalizados. En esos casos, es posible que las implementaciones enumeradas en la sección anterior no sean suficientes. Si ese es el caso, puedes extender una de estas implementaciones de recursos inactivos o crear la tuya propia.

Si implementas tu propia funcionalidad de recursos inactivos, ten en cuenta las siguientes prácticas recomendadas, en especial, la primera:

Invoca transiciones al estado inactivo fuera de las comprobaciones inactivas.
Una vez que tu app esté inactiva, llama a onTransitionToIdle() fuera de cualquier implementación de isIdleNow(). De esa manera, Espresso no realiza una segunda comprobación innecesaria para determinar si un recurso determinado está inactivo.

En el siguiente fragmento de código, se muestra un ejemplo de esta recomendación:

Kotlin

    fun isIdle() {
        // DON'T call callback.onTransitionToIdle() here!
    }

    fun backgroundWorkDone() {
        // Background work finished.
        callback.onTransitionToIdle() // Good. Tells Espresso that the app is idle.

        // Don't do any post-processing work beyond this point. Espresso now
        // considers your app to be idle and moves on to the next test action.
    }
    

Java

    public void isIdle() {
        // DON'T call callback.onTransitionToIdle() here!
    }

    public void backgroundWorkDone() {
        // Background work finished.
        callback.onTransitionToIdle() // Good. Tells Espresso that the app is idle.

        // Don't do any post-processing work beyond this point. Espresso now
        // considers your app to be idle and moves on to the next test action.
    }
    
Registra los recursos inactivos antes de que los necesites.

Los beneficios de sincronización asociados con los recursos inactivos solo tienen efecto después de que Espresso invoca por primera vez el método isIdleNow() de ese recurso.

En la siguiente lista, se muestran varios ejemplos de esta propiedad:

  • Si registras un recurso inactivo en un método anotado con @Before, el recurso inactivo se aplica en la primera línea de cada prueba.
  • Si registras un recurso inactivo dentro de una prueba, el recurso inactivo tendrá efecto durante la siguiente acción basada en Espresso. Este comportamiento aún ocurre incluso si la siguiente acción está en la misma prueba que la instrucción que registra el recurso inactivo.
Cancela el registro de los recursos inactivos una vez que hayas terminado de usarlos.

Para conservar los recursos del sistema, debes cancelar el registro de los recursos inactivos en cuanto dejes de necesitarlos. Por ejemplo, si registras un recurso inactivo en un método anotado con @Before, es mejor anular el registro de este recurso en un método correspondiente anotado con @After.

Usa un registro inactivo para registrar y cancelar el registro de recursos inactivos.

Si usas este contenedor para los recursos inactivos de la app, puedes registrar y cancelar el registro de los recursos inactivos repetidamente según sea necesario y, aun así, observar un comportamiento coherente.

Mantén solo el estado simple de la app dentro de los recursos inactivos.

Por ejemplo, los recursos inactivos que implementes y registres no deben contener referencias a objetos View.

Registra recursos inactivos

Espresso proporciona una clase de contenedor en la que puedes colocar los recursos inactivos de tu app. Esta clase, llamada IdlingRegistry, es un artefacto autónomo que introduce una sobrecarga mínima en tu app. La clase también te permite realizar los siguientes pasos para mejorar el mantenimiento de tu app:

  • Crear una referencia a IdlingRegistry, en lugar de los recursos inactivos que contiene, en las pruebas de tu app
  • Mantener las diferencias en la colección de recursos inactivos que utilizas para cada variante de compilación
  • Definir recursos inactivos en los servicios de tu app, en lugar de en los componentes de la IU que hacen referencia a esos servicios

Integra recursos inactivos en tu app

Aunque puedes agregar recursos inactivos a una app de diferentes maneras, un enfoque en particular mantiene el encapsulamiento de tu app y te permite especificar una operación determinada que representa un recurso inactivo específico.

Si agregas recursos inactivos a tu app, te recomendamos realizar solo el registro y la cancelación del registro de las operaciones en las pruebas, y la colocación de la lógica de recursos inactivos en el código de producción de la app.

Aunque crees la situación inusual de usar una interfaz de solo prueba en el código de producción siguiendo este enfoque, puedes unir los recursos inactivos al código que ya tienes, manteniendo el tamaño del APK de la aplicación y el recuento del método.

Enfoques alternativos

Si prefieres no tener la lógica de los recursos inactivos en el código de producción de tu app, hay varias otras estrategias de integración posibles:

  • Crea variantes de compilación, como los tipos de productos de Gradle, y usa recursos inactivos solo en la compilación de depuración de la app.
  • Usa un framework de inyección de dependencia, como Dagger, para insertar el gráfico de dependencia de recursos inactivos de la app en las pruebas. Si usas Dagger 2, la inyección debe provenir de un subcomponente.
  • Implementa un recurso inactivo en las pruebas de tu app y expone la parte de la implementación de la app que se debe sincronizar en esas pruebas.

    Precaución: Aunque esta decisión de diseño parece crear una referencia autónoma a los recursos inactivos, también rompe la encapsulación en todas las apps excepto en las más simples.

Recursos adicionales

Para obtener más información sobre cómo usar Espresso en las pruebas de Android, consulta los siguientes recursos.

Ejemplos