Aspectos básicos de las pruebas de apps para Android

En esta página, se describen los principios básicos de la prueba de apps para Android, incluidas las prácticas recomendadas centrales y sus beneficios.

Beneficios de las pruebas

Probar la app es una parte integral del proceso de desarrollo. Cuando ejecutas pruebas de la app de manera constante, puedes verificar la precisión, el comportamiento funcional y la usabilidad de la app antes de lanzarla públicamente.

Puedes navegar por la app para probar manualmente. Puedes usar diferentes dispositivos y emuladores, cambiar el idioma del sistema y tratar de generar cada error del usuario o recorrer cada flujo de usuarios.

Sin embargo, las pruebas manuales escalan de forma deficiente y puede ser fácil pasar por alto las regresiones en el comportamiento de la app. Las pruebas automatizadas implican el uso de herramientas que realizan pruebas por ti, lo que es más rápido, más repetible y, por lo general, te brinda comentarios más prácticos sobre la app en las primeras etapas del proceso de desarrollo.

Tipos de pruebas en Android

Las aplicaciones para dispositivos móviles son complejas y deben funcionar bien en muchos entornos. Por lo tanto, hay muchos tipos de pruebas.

Asunto

Por ejemplo, hay diferentes tipos de pruebas según el tema:

  • Pruebas funcionales: ¿Mi app hace lo que se supone que debe hacer?
  • Pruebas de rendimiento: ¿lo hace de forma rápida y eficiente?
  • Pruebas de accesibilidad: ¿Funciona bien con los servicios de accesibilidad?
  • Pruebas de compatibilidad: ¿Funciona bien en todos los dispositivos y niveles de API?

Alcance

Las pruebas también varían según el tamaño o el grado de aislamiento:

  • Las pruebas de unidades o pequeñas solo verifican una parte muy pequeña de la app, como un método o una clase.
  • Las pruebas de extremo a extremo o las pruebas importantes verifican partes más grandes de la app al mismo tiempo, como una pantalla completa o un flujo de usuarios.
  • Las pruebas de nivel intermedio se encuentran en el medio y verifican la integración entre dos o más unidades.
Las pruebas pueden ser pequeñas, medianas o grandes.
Figura 1: Prueba los alcances en una aplicación típica.

Hay muchas formas de clasificar las pruebas. Sin embargo, la distinción más importante para los desarrolladores de apps es dónde se ejecutan las pruebas.

Comparación entre pruebas instrumentadas y locales

Puedes ejecutar las pruebas en un dispositivo Android o en otra computadora:

  • Las pruebas de instrumentación se ejecutan en un dispositivo Android, ya sea físico o emulado. La app se compila y se instala junto con una app de prueba que inserta comandos y lee el estado. Por lo general, las pruebas instrumentadas son pruebas de IU, que inician una app y, luego, interactúan con ella.
  • Las pruebas locales se ejecutan en tu máquina de desarrollo o en un servidor, por lo que también se denominan pruebas del host. Por lo general, son pequeños y rápidos, y aíslan al sujeto de prueba del resto de la app.
Se pueden ejecutar como pruebas de instrumentación en un dispositivo o como pruebas locales en tu máquina de desarrollo.
Figura 2: Diferentes tipos de pruebas según dónde se ejecuten.

No todas las pruebas de unidades son locales, y no todas las pruebas de extremo a extremo se ejecutan en un dispositivo. Por ejemplo:

  • Prueba local grande: Puedes usar un simulador de Android que se ejecute de manera local, como Robolectric.
  • Prueba de instrumentación pequeña: Puedes verificar que tu código funcione bien con una función del framework, como una base de datos SQLite. Puedes ejecutar esta prueba en varios dispositivos para verificar la integración con varias versiones de SQLite.

Ejemplos

En los siguientes fragmentos, se muestra cómo interactuar con la IU en una prueba de IU instrumentada que hace clic en un elemento y verifica que se muestre otro.

Espresso

// When the Continue button is clicked
onView(withText("Continue"))
    .perform(click())

// Then the Welcome screen is displayed
onView(withText("Welcome"))
    .check(matches(isDisplayed()))

IU de Compose

// When the Continue button is clicked
composeTestRule.onNodeWithText("Continue").performClick()

// Then the Welcome screen is displayed
composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()

Este fragmento muestra parte de una prueba de unidades para un ViewModel (prueba local del host):

// Given an instance of MyViewModel
val viewModel = MyViewModel(myFakeDataRepository)

// When data is loaded
viewModel.loadData()

// Then it should be exposing data
assertTrue(viewModel.data != null)

Define una estrategia de prueba

En un mundo ideal, deberías probar cada línea de código de tu app en todos los dispositivos con los que esta sea compatible. Lamentablemente, este enfoque es demasiado lento y costoso para ser práctico.

Una buena estrategia de prueba encuentra un equilibrio adecuado entre la fidelidad de una prueba, su velocidad y su confiabilidad. La similitud del entorno de pruebas con un dispositivo real determina la fidelidad de la prueba. Las pruebas de fidelidad más alta se ejecutan en dispositivos emulados o en el propio dispositivo físico. Las pruebas de menor fidelidad pueden ejecutarse en la JVM de tu estación de trabajo local. Las pruebas de alta fidelidad suelen ser más lentas y requieren más recursos, por lo que no todas deberían ser pruebas de alta fidelidad.

Pruebas inestables

Los errores se producen incluso en las ejecuciones de prueba implementadas y diseñadas correctamente. Por ejemplo, cuando se ejecuta una prueba en un dispositivo real, es posible que se inicie una actualización automática en medio de una prueba y haga que falle. Las condiciones de carrera sutiles en tu código pueden ocurrir solo en un pequeño porcentaje de las veces. Las pruebas que no la superan el 100% del tiempo son frágiles.

Arquitectura comprobable

Con una arquitectura de app que se puede probar, el código sigue una estructura que te permite probar con facilidad diferentes partes de forma aislada. Las arquitecturas que se pueden probar tienen otras ventajas, como una mejor legibilidad, capacidad de mantenimiento, escalabilidad y reutilización.

Una arquitectura que no se puede probar produce lo siguiente:

  • Pruebas más grandes, más lentas y más inestables. Es posible que las clases que no se pueden probar por unidades deban estar cubiertas por pruebas de integración o de IU más grandes.
  • Menos oportunidades para probar diferentes situaciones. Las pruebas más grandes son más lentas, por lo que probar todos los estados posibles de una app podría no ser realista.

Para obtener más información sobre los lineamientos de arquitectura, consulta la guía de arquitectura de apps.

Enfoques para la separación

Si puedes extraer parte de una función, clase o módulo del resto, probarlo es más fácil y eficaz. Esta práctica se conoce como separación y es el concepto más importante para la arquitectura que se puede probar.

Las técnicas comunes de separación incluyen las siguientes:

  • Divide una app en capas, como Presentación, Dominio y Datos. También puedes dividir una app en módulos, uno por función.
  • Evita agregar lógica a entidades que tengan dependencias grandes, como actividades y fragmentos. Usa estas clases como puntos de entrada al framework y traslada la IU y la lógica empresarial a otro lugar, como a un elemento componible, ViewModel o una capa de dominio.
  • Evita las dependencias de framework directas en las clases que contienen lógica empresarial. Por ejemplo, no uses contextos de Android en ViewModels.
  • Haz que las dependencias sean fáciles de reemplazar. Por ejemplo, usa interfaces en lugar de implementaciones concretas. Usa la inserción de dependencias incluso si no usas un framework de DI.

Próximos pasos

Ahora que ya sabes por qué debes realizar las pruebas y los dos tipos principales de pruebas, puedes leer Qué debes probar.

Como alternativa, si quieres crear tu primera prueba y aprender mediante la práctica, consulta los codelabs de prueba.