Recursos inactivos de Espresso

Un recurso inactivo representa una operación asíncrona cuyos resultados afectan las operaciones posteriores de una prueba de IU. Si registras recursos inactivos con Espresso, puedes validar estas operaciones asíncronas de manera más confiable cuando pruebas tu 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 conoce ninguna otra operación asíncrona, 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 recursos inactivos cuando pruebas los resultados del trabajo asíncrono 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 ajustan bien, ya que es posible que tu app deba realizar un trabajo asíncrono más lento en una versión futura.
  • Implementa wrappers de reintento,que usan un bucle para verificar repetidamente si la app aún está realizando un trabajo asíncrono hasta que se agota el tiempo de espera. Incluso si especificas un recuento máximo de reintentos en las pruebas, cada reejecución consumirá los recursos del sistema, en particular la CPU.
  • Con instancias de CountDownLatch, que permiten que uno o más subprocesos esperen hasta que se complete una cantidad específica de operaciones que se ejecutan en otro subproceso. Estos objetos requieren que especifiques una duración de tiempo de espera; de lo contrario, tu app podría bloquearse indefinidamente. Los bloqueos también agregan complejidad innecesaria a tu código, lo que dificulta el mantenimiento.

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

Casos de uso comunes

Cuando realices en tus pruebas operaciones similares a las de los siguientes ejemplos, considera 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 validen.

Ejemplo de implementaciones de recursos inactivos

En la siguiente lista, se describen varias implementaciones de ejemplo de recursos inactivos que puedes integrar en tu app:

CountingIdlingResource
Realiza un recuento de tareas activas. Cuando el contador 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 asíncrono de tu app durante las pruebas.
UriIdlingResource
Similar a CountingIdlingResource, pero el contador 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, en las que una app del subproceso puede realizar una solicitud nueva 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 contador de tareas activas.
IdlingScheduledThreadPoolExecutor
Es una implementación personalizada de ScheduledThreadPoolExecutor. Proporciona las mismas funciones 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

A medida que usas recursos inactivos en las pruebas de la app, es posible que debas proporcionar administración o 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.

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

Invoca transiciones al estado inactivo fuera de las comprobaciones inactivas.
Una vez que tu app quede inactiva, llama a onTransitionToIdle() fuera de cualquier implementaciones de isIdleNow(). De esa manera, Espresso no realiza una segunda verificación innecesaria para determinar si un recurso inactivo 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 se aplican si 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, ese recurso se aplica durante la siguiente acción basada en Espresso. Este comportamiento 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 ya no los necesites. Por ejemplo, si registras un recurso inactivo en un método anotado con @Before, se recomienda cancelar el registro de este recurso en un método correspondiente que tenga la anotación @After.

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

Si usas este contenedor para los recursos inactivos de tu app, puedes registrar y cancelar el registro de recursos inactivos varias veces 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.

Cómo registrar 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. Además, te permite seguir los pasos que se indican a continuación para mejorar la capacidad de mantenimiento de tu app:

  • Crea una referencia a IdlingRegistry, en lugar de a los recursos inactivos de que contiene, en las pruebas de la app.
  • Mantén las diferencias en la colección de recursos inactivos que usas para cada variante de compilación.
  • Define recursos inactivos en los servicios de tu app, en lugar de en los componentes de la IU que hacen referencia a esos servicios.

Cómo integrar recursos inactivos en tu app

Si bien puedes agregar recursos inactivos a una app de diferentes maneras, un enfoque en particular mantiene el encapsulamiento de la app y te permite especificar una operación específica que representa un recurso inactivo determinado.

Si agregas recursos inactivos a tu app, te recomendamos colocar la lógica de recursos inactivos en la app y realizar solo las operaciones de registro y cancelación de registro en las pruebas.

Si bien sigues este enfoque para crear la situación inusual de usar una interfaz de solo prueba en el código de producción, puedes unir los recursos inactivos al código que ya tienes y mantener el tamaño del APK de la app y la cantidad de métodos.

Enfoques alternativos

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

  • 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 tu app.
  • Usa un framework de inyección de dependencias, 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 la 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 independiente 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 el uso de Espresso en pruebas de Android, consulta los siguientes recursos.

Ejemplos