Aspectos básicos de las pruebas

Los usuarios interactúan con la app en distintos niveles, desde presionar un botón hasta descargar información en los 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.

Cómo crear y probar el código de forma iterativa

Para comenzar a desarrollar una función de manera iterativa, es probable que escribas una prueba nueva o agregues casos y aserciones a una prueba de unidades existente. Sin embargo, 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 función nueva. Para cada unidad, escribe una prueba de unidades correspondiente. Las pruebas de unidades deberían casi agotar todas las interacciones posibles con la unidad, incluidas las interacciones estándar, las entradas no válidas y los casos con recursos que no están disponibles. Aprovecha las bibliotecas de Jetpack siempre que sea posible; cuando uses las bibliotecas que ya están probadas, podrás concentrarte en validar el comportamiento específico de la app.

El ciclo de desarrollo de pruebas consiste en escribir una prueba de unidades fallida, luego escribir código para pasarla y, por último, 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 controlado 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.

Cómo visualizar la 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 represente una tarea específica que los usuarios completan en la app. Esta perspectiva difiere de la vista basada en pilas de una app, que suele tener 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.

A fin de obtener más información sobre cómo definir módulos en la app y 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 la app.
  • El directorio test debe contener las pruebas que se ejecutan en la máquina local, como las pruebas de unidades.

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 la memoria proporciona pruebas más eficaces que las emulaciones de los resultados de las bases de datos.

Específicamente, las instancias ficticias de tipos que no son de tu propiedad suelen dirigir a pruebas endebles que solo funcionan si comprendes las complejidades de la implementación de ese tipo por parte de un tercero. Usa las ubicaciones ficticias solo como último recurso. Es correcto generar ubicaciones ficticias de tus propios objetos, pero recuerda que las ubicaciones ficticias con anotaciones @Spy tienen 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 pruebas

Una pirámide con 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 la app

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

  • Las pruebas de nivel inferior son pruebas de unidades que validan el comportamiento de la 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 las interacciones entre módulos relacionados.
  • Las pruebas de nivel superior son pruebas de extremo a extremo que validan la trayectoria de los usuarios en 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% de nivel inferior, 20% de nivel intermedio y 10% de nivel superior.

Para obtener más información sobre la pirámide de pruebas de Android, consulta el video de la sesión de Test-Driven Development on 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 deben ser pruebas de unidades altamente enfocadas que validen 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 las pruebas dependen del framework de Android, usa una API unificada independiente del dispositivo, como las API de androidx.test. Esta coherencia te permite ejecutar la prueba de forma local sin un dispositivo físico ni un emulador.

Si las pruebas se basan en recursos, habilita la opción includeAndroidResources en el archivo build.gradle de la app. Así, 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 se ejecutan siempre en una máquina de desarrollo de una JVM, puedes usar Robolectric.

Robolectric simula el entorno de ejecución de Android 4.1 (API nivel 16) y versiones posteriores, y proporciona emulaciones mantenidas por la comunidad, denominadas sombras. Esta funcionalidad permite probar el código que depende del framework 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 con @UiThreadTest.

Cómo escribir pruebas de 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 de alcance) para definir la mejor manera de representar grupos de unidades en la app:

  1. Interacciones entre una vista y un modelo de vista, como 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. (esa 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 la app (a diferencia de los otros tipos de pruebas de nivel intermedio mencionados en la lista, este tipo de prueba en general requiere un dispositivo real porque la interacción durante las pruebas involucra varios elementos de la IU)

Para hacer estas pruebas, realiza las siguientes tareas:

  1. Utiliza los métodos de la biblioteca de Intents de Espresso. Para simplificar la información que pasas a las 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 de 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:

  • Ejecución de acciones en objetos View
  • Evaluación de cómo los usuarios con necesidades de accesibilidad pueden usar tu app
  • Ubicación y activación de elementos en objetos RecyclerView y AdapterView
  • Validación del estado de intents salientes
  • Verificación de la estructura de un DOM dentro de objetos WebView

Para obtener más información sobre estas interacciones y 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 validar una app que esté lo más cerca posible del producto final real para minimizar el efecto.

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, por verticales funcionales o por 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 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 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.

Cómo crear aserciones más legibles con Truth

El equipo de Guava proporciona una biblioteca de aserciones fluida denominada Truth. Puedes usar esta biblioteca como alternativa a las aserciones basadas en JUnit o Hamcrest cuando construyes el paso de validación, o 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 aserciones basadas en Truth sean aún más fáciles de construir:

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

Cómo escribir pruebas para la IU

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

Cómo ejecutar 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 las 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 la app, consulta la guía de Android Test Orchestrator.

Cómo interactuar con elementos visibles

La API de UI Automator permite interactuar con los elementos visibles de un dispositivo, sin importar la actividad ni 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. Como 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 nuevo lanzamiento 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

Incluye la anotación @AccessibilityChecks al comienzo del conjunto de pruebas para habilitar las comprobaciones de accesibilidad, 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

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

Para obtener más información sobre cómo interpretar los resultados de las 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();
    }
    

Cómo impulsar la actividad y los ciclos de vida de los fragmentos

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

Cómo administrar 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.

Cómo evaluar 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 la 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