Únete a ⁠ #Android11: The Beta Launch Show el 3 de junio.

Aspectos básicos de las pruebas

Los usuarios interactúan con tu app en una variedad de niveles, desde cuando presionan un botón hasta cuando descargan información en sus dispositivos. En consecuencia, debes probar una variedad de casos de uso e interacciones a medida que desarrollas tu app de forma iterativa.

Cómo organizar el código para pruebas

A medida que tu app se expande, es posible que sea necesario obtener datos de un servidor, interactuar con los sensores del dispositivo, acceder al almacenamiento local o procesar interfaces de usuario complejas. La versatilidad de tu app exige una estrategia de prueba integral.

Crea y prueba el código de forma iterativa

Cuando desarrollas una función de manera iterativa, comienzas escribiendo una nueva prueba o agregando casos y aserciones a una prueba de unidades existente. La prueba falla al principio porque la función aún no se implementó.

Es importante tener en cuenta las unidades de responsabilidad que surgen cuando diseñas la nueva función. Para cada unidad, escribe una prueba de unidad correspondiente. Las pruebas de tu unidad deben agotar casi todas las posibles interacciones con la unidad, incluidas las interacciones estándar, las entradas no válidas y los casos en los que los recursos no están disponibles. Aprovecha las bibliotecas de Jetpack siempre que sea posible; usando estas bibliotecas que ya están probadas, podrás concentrarte en validar el comportamiento específico de tu app.

El ciclo de desarrollo de pruebas consiste en escribir una prueba de unidad fallida, escribir código para pasarla y, luego, refactorizar. El ciclo completo de desarrollo de funciones existe dentro de un paso de un ciclo más grande basado en la IU.
Figura 1: Los dos ciclos asociados con el desarrollo iterativo, basado en pruebas

El flujo de trabajo completo, como se muestra en la Figura 1, contiene una serie de ciclos iterativos y anidados en los que un ciclo largo y lento impulsado por la IU prueba la integración de las unidades del código. Puedes probar las unidades con ciclos de desarrollo más cortos y más rápidos. Este conjunto de ciclos continúa hasta que la app satisface todos los casos de uso.

Visualiza tu app como una serie de módulos

Para facilitar la prueba del código, desarróllalo en términos de módulos, donde cada módulo representa una tarea específica que los usuarios completan dentro de tu app. Esta perspectiva contrasta la vista basada en pilas de una app, que normalmente contiene capas que representan la IU, la lógica empresarial y los datos.

Por ejemplo, una app de "lista de tareas" podría tener módulos para crear tareas, ver estadísticas sobre tareas completadas y tomar fotografías con el fin de asociarlas a una tarea determinada. Dicha arquitectura modular también te ayuda a mantener desacopladas las clases no relacionadas y proporciona una estructura natural para asignar la propiedad dentro de tu equipo de desarrollo.

Es importante establecer límites bien definidos en torno a cada módulo y crear nuevos módulos a medida que la app crece en escala y complejidad. Cada módulo debe tener solo un área de enfoque, y las API que permiten la comunicación entre módulos deben ser coherentes. Para que sea más fácil y rápido probar estas interacciones entre módulos, procura crear implementaciones emuladas de los módulos. En las pruebas, la implementación real de un módulo puede llamar a la implementación emulada del otro módulo.

Sin embargo, cuando crees un nuevo módulo, mantén cierta flexibilidad y no te preocupes si no contiene todas las funciones de inmediato. Está bien si un módulo determinado no tiene una o más capas de la pila de la app.

Para obtener más información sobre cómo definir módulos en tu app y sobre la compatibilidad de la plataforma para crear y publicar módulos, consulta Android App Bundles.

Cómo configurar el entorno de prueba

Si configuras tu entorno y las dependencias para crear pruebas en tu app, sigue las prácticas recomendadas que se describen en esta sección.

Organiza directorios de prueba basados en el entorno de ejecución

Un proyecto típico en Android Studio contiene dos directorios donde colocar las pruebas. Organiza las pruebas de la siguiente manera:

  • El directorio androidTest debe contener las pruebas que se ejecutan en dispositivos reales o virtuales. Dichas pruebas incluyen pruebas de integración, de extremo a extremo y otras, donde la JVM por sí sola no puede validar la funcionalidad de tu app.
  • El directorio test debe contener las pruebas que se ejecutan en tu máquina local, como las pruebas de unidad.

Considera las ventajas de ejecutar pruebas en diferentes tipos de dispositivos

Si ejecutas las pruebas en un dispositivo, puedes elegir entre los siguientes tipos:

  • Dispositivo real
  • Dispositivo virtual (como el emulador en Android Studio)
  • Dispositivo simulado (como Robolectric)

Los dispositivos reales ofrecen la mayor calidad, pero también requieren más tiempo para ejecutar las pruebas. Los dispositivos simulados, por otra parte, proporcionan una velocidad de prueba mejorada a costa de una calidad más baja. Sin embargo, las mejoras de la plataforma en recursos binarios y generadores de bucles realistas permiten que los dispositivos simulados produzcan resultados más realistas.

Los dispositivos virtuales ofrecen un equilibrio entre calidad y velocidad. Si usas dispositivos virtuales para pruebas, usa instantáneas para minimizar el tiempo de configuración entre las pruebas.

Determina si usarás objetos dobles de prueba

Cuando creas pruebas, tienes la opción de crear objetos reales o dobles de prueba, como objetos emulados o bien objetos ficticios. En general, es mejor usar objetos reales en las pruebas objetos dobles de prueba, en especial si el objeto que pruebas cumple con una de las siguientes condiciones:

  • El objeto es un objeto de datos.
  • El objeto no puede funcionar, a menos que se comunique con la versión del objeto real de una dependencia. Un buen ejemplo es un controlador de devolución de llamada de eventos.
  • Es difícil replicar la comunicación del objeto con una dependencia. Un buen ejemplo es un controlador de base de datos SQL, donde una base de datos en memoria proporciona pruebas más eficaces que las emulaciones de los resultados de las bases de datos.

En particular, las instancias ficticias de tipos que no son de tu propiedad, en general, conducen a pruebas poco eficaces que funcionan solo si comprendes las complejidades de la implementación de ese tipo por parte de un tercero. Usa estas ubicaciones ficticias solo como último recurso. Es correcto generar ubicaciones ficticias de tus propios objetos, pero en cuenta que las ubicaciones ficticias con anotaciones @Spy proporcionan más fidelidad que las que excluyen todas las funcionalidades dentro de una clase.

Sin embargo, se recomienda crear objetos emulados o incluso ficticios si las pruebas intentan realizar los siguientes tipos de operaciones en un objeto real:

  • Operaciones largas, como el procesamiento de un archivo grande
  • Acciones no herméticas, como la conexión a un puerto abierto arbitrario
  • Configuraciones difíciles de crear

Sugerencia: Consulta a los autores de la biblioteca si proporcionaron infraestructuras de prueba compatibles oficialmente, como emulaciones, en las que puedas confiar.

Cómo escribir tus pruebas

Después de configurar el entorno de prueba, debes escribir pruebas que evalúen la funcionalidad de tu app. En esta sección, se describe cómo escribir pruebas de nivel inferior, intermedio y superior.

Niveles de la pirámide de prueba

Pirámide que contiene tres capas
Figura 2: Pirámide de prueba, que muestra las tres categorías de pruebas que debes incluir en el conjunto de pruebas de tu app

La pirámide de prueba, que se muestra en la Figura 2, ilustra cómo la app debe incluir las tres categorías de pruebas: nivel inferior, nivel intermedio y nivel superior.

  • Las pruebas de nivel inferior son pruebas de unidades que validan el comportamiento de tu app de a una clase por vez.
  • Las pruebas de nivel intermedio son pruebas de integración que validan las interacciones entre los niveles de la pila dentro de un módulo o bien las interacciones entre módulos relacionados.
  • Las pruebas de nivel superior son pruebas de extremo a extremo que validan el desplazamiento de los usuarios que abarca varios módulos de la app.

A medida que trabajas en la pirámide, desde pruebas de nivel inferior hasta pruebas de nivel superior, en cada prueba aumenta la fidelidad, pero también aumenta el tiempo de ejecución y el esfuerzo para mantener y depurar la app. Por lo tanto, debes escribir más pruebas de unidades que pruebas de integración y más pruebas de integración que pruebas de extremo a extremo. Aunque la proporción de las pruebas de cada categoría puede variar según los casos prácticos de la app, en general, recomendamos la siguiente división entre las categorías: 70% en nivel inferior, 20% en nivel intermedio y 10% en nivel inferior.

Para obtener más información sobre la pirámide de pruebas de Android, consulta el video de la sesión Desarrollo mediante pruebas en Android de Google I/O 2017, a partir del minuto 1:51.

Escribe pruebas para el nivel inferior

Las pruebas de nivel inferior que escribes se deben concentrar en pruebas de unidades que validan de forma exhaustiva la funcionalidad y los contratos de cada clase dentro de la app.

A medida que agregas y cambias métodos dentro de una clase en particular, crea y ejecuta pruebas de unidades para ellos. Si estas pruebas dependen del marco de trabajo de Android, usa una API unificada independiente del dispositivo, como las API androidx.test. Esta coherencia te permite ejecutar la prueba localmente sin un dispositivo físico o un emulador.

Si las pruebas se basan en recursos, habilita la opción includeAndroidResources en el archivo build.gradle de tu app. Las pruebas de unidades pueden acceder a versiones compiladas de tus recursos, lo que permite que las pruebas se ejecuten de manera más rápida y precisa.

app/build.gradle

    android {
        // ...

        testOptions {
            unitTests {
                includeAndroidResources = true
            }
        }
    }
    

Pruebas de unidades locales

Utiliza las API de prueba de AndroidX siempre que sea posible para que las pruebas de unidades se puedan ejecutar en un dispositivo o en un emulador. Para las pruebas que siempre se ejecutan en una máquina de desarrollo JVM, puedes usar Robolectric.

Robolectric simula el tiempo de ejecución de Android 4.1 (API nivel 16) o una versión posterior, y proporciona emulaciones mantenidas por la comunidad, denominadas sombras. Esta funcionalidad permite probar el código que depende del marco de trabajo sin necesidad de usar un emulador ni objetos ficticios. Robolectric admite los siguientes aspectos de la plataforma de Android:

  • Ciclos de vida de los componentes
  • Bucles de eventos
  • Todos los recursos

Pruebas de unidades instrumentadas

Puedes ejecutar pruebas de unidades instrumentadas en un dispositivo físico o en un emulador. Sin embargo, esta forma de prueba implica tiempos de ejecución mucho más lentos que los correspondientes a las pruebas de unidades locales, por lo que se recomienda confiar en este método solo cuando es esencial para evaluar el comportamiento de la app frente al hardware real del dispositivo.

Al ejecutar pruebas instrumentadas, AndroidX Test utiliza los siguientes subprocesos:

  • El subproceso principal, también conocido como "subproceso de la IU" o "subproceso de la actividad", es el lugar donde se producen eventos de interacciones con la IU y el ciclo de vida de la actividad.
  • El subproceso de instrumentación es el lugar donde se ejecutan la mayoría de las pruebas. Cuando comienza el conjunto de pruebas, la clase AndroidJUnitTest inicia este subproceso.

Si necesitas una prueba para ejecución en el subproceso principal, haz una anotación usando @UiThreadTest.

Escribe pruebas para el nivel intermedio

Además de probar cada unidad de tu app mediante la ejecución de pruebas de nivel inferior, debes validar el comportamiento de tu app desde nivel del módulo. Para ello, escribe pruebas de nivel intermedio, que son pruebas de integración que validan la colaboración y la interacción de un grupo de unidades.

Usa la estructura de la app y los siguientes ejemplos de pruebas de nivel intermedio (en orden creciente) para definir la mejor manera de representar grupos de unidades en tu app:

  1. Interacciones entre una vista y un modelo de vista, por ejemplo, probar un objeto Fragment, validar el XML del diseño o evaluar la lógica de vinculación de datos de un objeto ViewModel.
  2. Pruebas en la capa de repositorio de tu app, que verifican que las diferentes fuentes de datos y los objetos de acceso a datos (DAO) interactúan de la forma esperada.
  3. Porciones verticales de la app, que prueban las interacciones en una pantalla determinada. Dicha prueba verifica las interacciones en todas las capas de la pila de tu app.
  4. Pruebas de varios fragmentos que evalúan un área específica de tu app. A diferencia de los otros tipos de pruebas de nivel intermedio mencionados en esta lista, este tipo de prueba en general requiere un dispositivo real porque la interacción durante las pruebas involucra múltiples elementos de la IU.

Para llevar a cabo estas pruebas, realiza las siguientes tareas:

  1. Utiliza los métodos de la biblioteca Espresso Intents. Para simplificar la información que pasas a estas pruebas, usa emulaciones y stubbing.
  2. Combina el uso de afirmaciones basadas en IntentSubject y en Truth para verificar los intents capturados.

Usa Espresso cuando ejecutes pruebas instrumentadas del nivel intermedio

Espresso ayuda a mantener las tareas sincronizadas mientras realizas interacciones de la IU similares a las siguientes en un dispositivo o en Robolectric:

  • Acciones en objetos View.
  • Evaluación de la manera en la que los usuarios con necesidades de accesibilidad pueden usar tu app.
  • Localización y activación de elementos dentro de los objetos RecyclerView y AdapterView.
  • Validación del estado de los intents salientes.
  • Verificación de la estructura de un DOM dentro de objetos WebView.

Para obtener más información sobre estas interacciones y sobre cómo usarlas en las pruebas de la app, consulta la guía de Espresso.

Escribe pruebas para el nivel superior

Si bien es importante probar cada clase y módulo en tu app de forma aislada, también lo es validar los flujos de trabajo de extremo a extremo que guían a los usuarios a través de varios módulos y funciones. Este tipo de pruebas forman cuellos de botella inevitables en el código, pero puedes minimizar este efecto validando una app que esté lo más cerca posible del producto final real.

Si tu app es lo suficientemente pequeña, es posible que solo necesites un conjunto de pruebas de nivel superior para evaluar la funcionalidad de la app en su totalidad. De lo contrario, deberás dividir los conjuntos de pruebas de nivel superior por propiedad de equipo, verticales funcionales o bien objetivos del usuario.

Por lo general, es mejor probar la app en un dispositivo emulado o en un servicio basado en la nube, como Firebase Test Lab, en lugar de en un dispositivo físico, ya que puedes probar varias combinaciones de tamaños de pantalla y configuraciones de hardware de manera más fácil y rápida.

Compatibilidad con sincronización en Espresso

Además de admitir pruebas de instrumentación de nivel intermedio, Espresso admite la sincronización cuando se completan las siguientes tareas en pruebas de nivel superior:

  • Flujos de trabajo que cruzan los límites del proceso de tu app. Disponible solo en Android 8.0 (API nivel 26) y versiones posteriores.
  • Seguimiento de operaciones en segundo plano de larga duración dentro de la app.
  • Ejecución de pruebas fuera del dispositivo.

Para obtener más información sobre estas interacciones y sobre cómo usarlas en las pruebas de la app, consulta la guía de Espresso.

Cómo completar otras tareas de prueba con AndroidX Test

En esta sección, se describe cómo usar elementos de AndroidX Test para definir aún más las pruebas de la app.

Crea afirmaciones más legibles con Truth

El equipo de Guava proporciona una biblioteca de afirmaciones fluida denominada Truth. Puedes usar esta biblioteca como alternativa a las afirmaciones basadas en JUnit o Hamcrest construyendo el paso de validación, o el paso then, de las pruebas.

Por lo general, Truth se usa para expresar que un objeto determinado tiene una propiedad específica mediante frases que contienen las condiciones que se están probando, por ejemplo:

  • assertThat(object).hasFlags(FLAGS)
  • assertThat(object).doesNotHaveFlags(FLAGS)
  • assertThat(intent).hasData(URI)
  • assertThat(extras).string(string_key).equals(EXPECTED)

AndroidX Test admite varios asuntos adicionales para Android con la finalidad de que las afirmaciones basadas en Truth sean aún más fáciles de construir:

La API de AndroidX Test te ayuda a llevar a cabo tareas comunes relacionadas con las pruebas de app para dispositivos móviles, que se analizan en las siguientes secciones.

Escribe pruebas para la IU

Espresso te permite localizar elementos de la IU e interactuar con ellos de forma programada en tu app protegiendo los subprocesos. Para obtener más información, consulta la guía de Espresso.

Ejecuta pruebas en la IU

La clase AndroidJUnitRunner define un panel de prueba JUnit basado en instrumentación que permite ejecutar clases de prueba de estilo JUnit 3 o JUnit 4 en dispositivos Android. El panel de prueba facilita la carga de tu paquete de prueba y de la app correspondiente en un dispositivo o emulador, ejecuta las pruebas e informa los resultados.

Para aumentar aún más la confiabilidad de estas pruebas, usa Android Test Orchestrator, que ejecuta cada prueba de la IU en su propia zona de pruebas Instrumentation. Esta arquitectura reduce el estado de uso compartido entre pruebas y aísla las fallas de la app por prueba. Para obtener más información sobre los beneficios que proporciona Android Test Orchestrator a medida que pruebas tu app, consulta la guía de Android Test Orchestrator.

Interactúa con elementos visibles

La API de UI Automator permite interactuar con los elementos visibles de un dispositivo, independientemente de la actividad o el fragmento enfocados.

Precaución: Recomendamos probar la app utilizando UI Automator solo si la app debe interactuar con la IU del sistema o con otra aplicación para cumplir con un caso práctico crítico. Debido a que UI Automator interactúa con una IU del sistema en particular, debes volver a ejecutar y corregir las pruebas de UI Automator después de cada actualización de la versión de la plataforma y después de cada nueva versión de los servicios de Google Play.

Como alternativa al uso de UI Automator, recomendamos agregar pruebas herméticas o separar la prueba de nivel superior en un conjunto de pruebas de nivel inferior y nivel intermedio. En particular, concéntrate en probar una instancia de comunicación entre aplicaciones por vez, por ejemplo, enviar información a otras apps y responder a resultados de intents. La herramienta Espresso-Intents puede ayudarte a escribir estas pruebas de nivel inferior.

Agrega comprobaciones de accesibilidad para validar la usabilidad general

La interfaz de la app debe permitir a todos los usuarios, incluidos aquellos con necesidades de accesibilidad, interactuar con el dispositivo y completar tareas con mayor facilidad en la app.

Con el fin de ayudar a validar la accesibilidad de tu app, la biblioteca de pruebas de Android proporciona varias funciones integradas, que se analizan en las siguientes secciones. Para obtener más información sobre cómo validar la usabilidad de tu app para diferentes tipos de usuarios, consulta la guía sobre cómo probar la accesibilidad de tu app.

Robolectric

Habilita las comprobaciones de accesibilidad incluyendo la anotación @AccessibilityChecks al comienzo del conjunto de pruebas, como se muestra en el siguiente fragmento de código:

Kotlin

    import org.robolectric.annotation.AccessibilityChecks

    @AccessibilityChecks
    class MyTestSuite {
        // Your tests here.
    }
    

Java

    import org.robolectric.annotation.AccessibilityChecks;

    @AccessibilityChecks
    public class MyTestSuite {
        // Your tests here.
    }
    

Espresso

Habilita las comprobaciones de accesibilidad llamando a AccessibilityChecks.enable() en el método setUp() del conjunto de pruebas, como se muestra en el siguiente fragmento de código.

Para obtener más información sobre cómo interpretar los resultados de estas comprobaciones de accesibilidad, consulta la guía de comprobación de accesibilidad de Espresso.

Kotlin

    import androidx.test.espresso.accessibility.AccessibilityChecks

    @Before
    fun setUp() {
        AccessibilityChecks.enable()
    }
    

Java

    import androidx.test.espresso.accessibility.AccessibilityChecks;

    @Before
    public void setUp() {
        AccessibilityChecks.enable();
    }
    

Impulsa la actividad y los ciclos de vida de los fragmentos

Usa las clases ActivityScenario y FragmentScenario para probar cómo responden las actividades y los fragmentos de tu app a las interrupciones y los cambios de configuración a nivel del sistema. Para obtener más información, consulta las guías sobre cómo probar actividades y fragmentos.

Administra los ciclos de vida del servicio

AndroidX Test incluye código para administrar los ciclos de vida de los servicios clave. Para obtener información sobre cómo definir estas reglas, consulta la guía de reglas JUnit4.

Evalúa todas las variantes de comportamiento que difieren según la versión del SDK

Si el comportamiento de la app depende de la versión del SDK del dispositivo, usa la anotación @SdkSuppress y pasa valores para minSdkVersion o maxSdkVersion, según cómo hayas ramificado la lógica de tu app:

Kotlin

    @Test
    @SdkSuppress(maxSdkVersion = 27)
    fun testButtonClickOnOreoAndLower() {
        // ...
    }

    @Test
    @SdkSuppress(minSdkVersion = 28)
    fun testButtonClickOnPieAndHigher() {
        // ...
    }
    

Java

    @Test
    @SdkSuppress(maxSdkVersion = 27)
    public void testButtonClickOnOreoAndLower() {
        // ...
    }

    @Test
    @SdkSuppress(minSdkVersion = 28)
    public void testButtonClickOnPieAndHigher() {
        // ...
    }
    

Recursos adicionales

Para obtener más información sobre las pruebas en Android, consulta los siguientes recursos.

Ejemplos

Codelabs