Cómo escribir pruebas automatizadas

1. Antes de comenzar

En este codelab, aprenderás sobre las pruebas automatizadas de Android y cómo te permiten escribir apps escalables y sólidas. También te familiarizarás con la diferencia entre la lógica de la IU y la lógica empresarial, y cómo probar ambas. Por último, aprenderás a escribir y ejecutar pruebas automatizadas en Android Studio.

Requisitos previos

  • Capacidad de escribir una app para Android con funciones y elementos componibles

Qué aprenderás

  • Qué hacen las pruebas automatizadas en Android
  • Por qué son importantes las pruebas automatizadas
  • Qué son las pruebas locales y para qué se usan
  • Qué es una prueba de instrumentación y para qué se usa
  • Cómo escribir pruebas locales para código Android
  • Cómo escribir pruebas de instrumentación para apps para Android
  • Cómo ejecutar pruebas automatizadas

Qué compilarás

  • Una prueba local
  • Una prueba de instrumentación

Requisitos

  • La versión más reciente de Android Studio
  • El código de la solución de la app de Tip Time

2. Obtén el código de partida

Descarga el código:

Como alternativa, puedes clonar el repositorio de GitHub para el código:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git
$ cd basic-android-kotlin-compose-training-tip-calculator
$ git checkout main

3. Pruebas automatizadas

En el contexto de un software, las pruebas son un método estructurado para verificar el software para garantizar que funcione correctamente. Las pruebas automatizadas son códigos que comprueban que otro fragmento del código que escribiste funcione correctamente.

Probar la app es una parte importante del proceso de desarrollo. Al ejecutar pruebas de la app de manera coherente, puedes verificar la corrección, el comportamiento funcional y la usabilidad de la app antes de lanzarla públicamente.

Las pruebas también proporcionan una manera de verificar continuamente el código existente a medida que se introducen cambios.

Si bien las pruebas manuales casi siempre se realizan en un lugar específico, las pruebas en Android a menudo se pueden automatizar. Durante lo que queda del curso, te enfocarás en las pruebas automatizadas para probar el código de la app y los requisitos funcionales de esta. En este codelab, aprenderás los conceptos básicos de las pruebas en Android. En los próximos, aprenderás más prácticas avanzadas de prueba de apps para Android.

A medida que te familiarices con el desarrollo y las pruebas de apps para Android, deberías escribir una prueba de manera periódica junto al código de tu app. Si creas una prueba cada vez que creas una función nueva en tu app, se reducirá la carga de trabajo más adelante cuando la app crezca. También es una forma conveniente de asegurarte de que tu app funciona de manera correcta sin invertir demasiado tiempo en probarla manualmente.

Las pruebas automatizadas son una parte esencial del desarrollo de software, y el desarrollo de Android no es la excepción. Por lo tanto, este es el momento indicado para hablar del tema.

Por qué son importantes las pruebas automatizadas

Al principio, puede parecer que no necesitas hacer pruebas en tu app. Sin embargo, las apps de todos los tamaños y complejidades necesitan pruebas.

Para ampliar tu base de código, debes probar la funcionalidad preexistente a medida que agregas nuevas partes, lo cual solo es posible si tienes pruebas preexistentes. A medida que tu app crece, las pruebas manuales requieren mucho más esfuerzo que las pruebas automatizadas. Además, una vez que comienzas a trabajar en apps en producción, las pruebas se vuelven esenciales cuando tienes una base de usuarios de mayor tamaño. Por ejemplo, debes tener en cuenta muchos tipos diferentes de dispositivos que ejecutan varias versiones de Android.

Con el tiempo, llegarás a un punto en el que las pruebas automatizadas puedan representar la mayoría de los casos de uso más rápido y de forma significativa que las pruebas manuales. Cuando ejecutas pruebas antes de lanzar código nuevo, puedes realizar cambios al existente a fin de evitar la actualización de una app con comportamientos inesperados.

Recuerda que las pruebas automatizadas son aquellas que se ejecutan mediante software, a diferencia de las manuales, que realizan las personas que interactúan de forma directa con un dispositivo. Las pruebas automatizadas y manuales desempeñan una función esencial a los efectos de garantizar que los usuarios de tu producto tengan una experiencia agradable. Sin embargo, las automatizadas pueden resultar más precisas y optimizar la productividad de tu equipo, ya que se pueden ejecutar mucho más rápido que una prueba manual y no se necesita que una persona las ejecute.

Tipo de pruebas automatizadas

Pruebas locales

Las pruebas locales son un tipo de prueba automatizada que prueban directamente un fragmento pequeño de código para garantizar que funcione correctamente. Con las pruebas locales, puedes probar funciones, clases y propiedades. Las pruebas locales se ejecutan en la estación de trabajo, lo que significa que se ejecutan en un entorno de desarrollo sin necesidad de un dispositivo o emulador. Es una forma sofisticada de decir que las pruebas locales se ejecutan en tu computadora. Además, tienen una sobrecarga muy baja de recursos informáticos, por lo que pueden ejecutarse rápido incluso con recursos limitados. Android Studio está listo para ejecutar pruebas de unidades automáticamente.

Pruebas de instrumentación

Para el desarrollo de Android, una prueba de instrumentación es una prueba de IU. Las pruebas de instrumentación te permiten probar partes de una app que dependen de la API de Android, así como las APIs y los servicios de su plataforma.

A diferencia de las pruebas locales, las pruebas de IU inician una app o parte de ella, simulan las interacciones del usuario y comprueban si la app reaccionó adecuadamente. A lo largo de este curso, las pruebas de IU se ejecutan en un dispositivo físico o emulador.

Cuando ejecutas una prueba de instrumentación en Android, el código de prueba en realidad se compila en su propio Paquete de aplicación para Android (APK), como una app para Android normal. Un APK es un archivo comprimido que contiene todo el código y los archivos necesarios para ejecutar la app en un dispositivo o emulador. Ese APK de prueba se instala en el dispositivo o emulador junto con el APK de la app normal. Luego, el APK de prueba ejecuta sus pruebas en el APK de la app.

4. Escribe una prueba local

Prepara el código de la app

Las pruebas locales prueban directamente los métodos desde el código de la app, por lo que los métodos que se deben probar deben estar disponibles para los métodos y las clases de prueba. La prueba local del siguiente fragmento de código garantiza que el método calculateTip() funcione correctamente, pero, actualmente, el método calculateTip() es private y, por lo tanto, no se puede acceder desde la prueba. Quita la designación private y haz que sea internal:

MainActivity.kt

internal fun calculateTip(amount: Double, tipPercent: Double = 15.0, roundUp: Boolean): String {
    var tip = tipPercent / 100 * amount
    if (roundUp) {
        tip = kotlin.math.ceil(tip)
    }
    return NumberFormat.getCurrencyInstance().format(tip)
}
  • En el archivo MainActivity.kt de la línea antes del método calculateTip(), agrega la anotación @VisibleForTesting:
@VisibleForTesting
internal fun calculateTip(amount: Double, tipPercent: Double = 15.0, roundUp: Boolean): String {
    var tip = tipPercent / 100 * amount
    if (roundUp) {
        tip = kotlin.math.ceil(tip)
    }
    return NumberFormat.getCurrencyInstance().format(tip)
}

Esto hace que el método sea público, pero les indica a otros que solo es público para realizar pruebas.

Cómo crear el directorio de prueba

En los proyectos de Android, el directorio test es donde se escriben las pruebas locales.

Para crear el directorio de prueba, haz lo siguiente:

  1. En la pestaña Project, cambia la vista a Project.

a6b5eade0103eca9.png

  1. Haz clic con el botón derecho en el directorio src.

d6bfede787910341.png

  1. Selecciona New > Directory.

a457c469e7058234.png

  1. En la ventana New Directory, selecciona test/java.

bd2c2ef635f0a392.png

  1. Presiona las teclas Intro o Volver del teclado. El directorio test ahora se puede ver en la pestaña Project.

d07872d354d8aa92.png

El directorio test requiere una estructura de paquete idéntica a la del directorio main en el que se encuentra el código de tu app. En otras palabras, así como el código de tu app está escrito en el paquete main > java > com > example > tiptime, las pruebas locales se escribirán en test > java > com > example > tiptime.

Crea esta estructura de paquetes en el directorio test:

  1. Haz clic con el botón derecho en el directorio test/java y selecciona New > Package.

99fcf5ff6cda7b57.png

  1. En la ventana New Package, escribe com.example.tiptime.

6223d2f5664ca35f.png

Cómo crear la clase de prueba

Ahora que el paquete test está listo, es hora de escribir algunas pruebas. Para comenzar, crea la clase de prueba.

  1. En la pestaña Project, haz clic en app > src > test y, luego, en la flecha de expansión d4706c21930a1ef3.png que se encuentra junto al directorio test.
  2. Haz clic con el botón derecho en el directorio com.example.tiptime y selecciona New > Kotlin Class/File.

5e9d46922b587fdc.png

  1. Ingresa TipCalculatorTests como el nombre de la clase.

9260eb95d7aa6095.png

Cómo escribir la prueba

Como se mencionó anteriormente, las pruebas locales se usan para probar pequeños fragmentos de código en la app. La función principal de la app de Tip Time calcula las propinas, por lo que debería haber una prueba local que garantice que la lógica de cálculo de las propinas funcione correctamente.

Para ello, debes llamar directamente a la función calculateTip() como lo hiciste en el código de la app. Luego, asegúrate de que el valor que muestra la función coincida con un valor esperado según los valores que pasaste a la función.

Hay algunos aspectos que debes tener en cuenta cuando escribes pruebas automatizadas. La siguiente lista de conceptos se aplica a las pruebas locales y de instrumentación. Al principio pueden parecer abstractos, pero te familiarizarás con ellos hacia el final de este codelab.

  • Escribe pruebas automatizadas en forma de métodos
  • Anota el método con la anotación @Test. Esto le indica al compilador que el método es de prueba y lo ejecuta en consecuencia.
  • Asegúrate de que el nombre describa claramente de qué se trata la prueba y cuál es el resultado esperado.
  • Los métodos de prueba no usan una lógica como los métodos normales de la app. No se ven afectados por las implementaciones. Verifican estrictamente el resultado esperado de una entrada determinada. Es decir, los métodos de prueba solo ejecutan un conjunto de instrucciones para confirmar que la IU o la lógica de una app funcionan correctamente. Aún no necesitas comprender qué significa esto, ya que observarás cómo se vería más adelante, pero recuerda que el código de prueba puede verse muy diferente del código de la app al que estás acostumbrado.
  • Por lo general, las pruebas terminan con una aserción, que se usa para garantizar que se cumpla una condición determinada. Las aserciones provienen de una llamada de método que tiene assert en su nombre. Por ejemplo, la aserción assertTrue() se usa comúnmente en las pruebas de Android. Las sentencias de aserción se usan en la mayoría de las pruebas, pero rara vez se utilizan en el código real de la app.

Escribe la prueba:

  1. Crea un método para probar el cálculo de una propina del 20% por un importe de factura de USD 10. El resultado esperado de ese cálculo es de USD 2.
import org.junit.Test

class TipCalculatorTests {

   @Test
   fun calculateTip_20PercentNoRoundup() {
       
   }
}

Como quizás recuerdes, el método calculateTip() del archivo MainActivity.kt en el código de la app requiere tres parámetros. El importe de la factura, el porcentaje de propina y una marca que indique si se redondea el resultado.

fun calculateTip(amount: Double, tipPercent: Double, roundUp: Boolean)

Cuando llega el momento de llamar a este método desde la prueba, estos parámetros deben pasarse como si se hubiera llamado al método en el código de la app.

  1. En el método calculateTip_20PercentNoRoundup(), crea dos variables constantes: una variable amount establecida en un valor 10.00 y una variable tipPercent establecida en un valor 20.00.
val amount = 10.00
val tipPercent = 20.00
  1. En el código de la app, en el archivo MainActivity.kt, observa el siguiente código; el importe de la propina tiene el formato correspondiente según la configuración regional del dispositivo.

MainActivity.kt

...
NumberFormat.getCurrencyInstance().format(tip)
...

Se debe usar el mismo formato para la verificación del importe de la propina esperado en la prueba.

  1. Crea una variable expectedTip establecida en NumberFormat.getCurrencyInstance().format(2).

La variable expectedTip se compara con el resultado del método calculateTip() más adelante. De esta manera, la prueba garantiza que el método funcione correctamente. En el último paso, configuraste la variable amount en 10.00 y la variable tipPercent en 20.00. El veinte por ciento de 10 es 2, por lo tanto, la variable expectedTip se establece en una moneda con formato con un valor de 2. Recuerda que el método calculateTip() muestra un valor String con formato.

  1. Llama al método calculateTip() con las variables amount y tipPercent, y pasa un argumento false para el redondeo.

En este caso, no necesitas tener en cuenta el redondeo porque el resultado esperado tampoco lo tiene en cuenta.

  1. Almacena el resultado de la llamada del método en una variable actualTip constante.

Hasta este punto, escribir esta prueba no difiere mucho de escribir un método normal en el código de la app. Sin embargo, ahora que tienes el valor que se muestra del método que deseas probar, debes determinar si ese valor es el correcto con una aserción.

Por lo general, hacer una aserción es el objetivo final de una prueba automatizada y no es algo que se use comúnmente en el código de la app. En este caso, debes asegurarte de que la variable actualTip sea igual a la variable expectedTip. Para ello, se puede usar el método assertEquals() de la biblioteca JUnit.

El método assertEquals() toma dos parámetros: un valor esperado y un valor real. Si esos valores son iguales, la aserción y la prueba se realizan correctamente. Si no son iguales, la aserción y la prueba fallan.

  1. Llama a este método assertEquals() y, luego, pasa las variables expectedTip y actualTip como parámetros:
import org.junit.Assert.assertEquals
import org.junit.Test
import java.text.NumberFormat

class TipCalculatorTests {

    @Test
    fun calculateTip_20PercentNoRoundup() {
        val amount = 10.00
        val tipPercent = 20.00
        val expectedTip = NumberFormat.getCurrencyInstance().format(2)
        val actualTip = calculateTip(amount = amount, tipPercent = tipPercent, false)
        assertEquals(expectedTip, actualTip)
    }
}

Ejecuta la prueba

Ahora es momento de ejecutar la prueba.

Tal vez hayas notado que aparecen flechas en el margen junto al número de línea del nombre de tu clase y la función de prueba. Puedes hacer clic en estas flechas para ejecutar la prueba. Cuando haces clic en la flecha junto a un método, solo ejecutas ese método de prueba. Si una clase tiene varios métodos de prueba, puedes hacer clic en la flecha que se encuentran junto a la clase para ejecutar todos los métodos de prueba de esa clase.

722bf5c7600bc004.png

Ejecuta la prueba:

  • Haz clic en las flechas que se encuentran junto a la declaración de la clase y, luego, en Run 'TipCalculatorTests'.

a294e77a57b0bb0a.png

Deberías ver lo siguiente:

  • En la parte inferior del panel Run, verás algunos resultados.

c97b205fef4da587.png

5. Cómo escribir una prueba de instrumentación

Cómo crear el directorio de instrumentación

El directorio de instrumentación se crea de manera similar al directorio de prueba local.

  1. Haz clic con el botón derecho en el directorio src y selecciona New > Directory.

309ea2bf7ad664e2.png

  1. En la ventana New Directory, selecciona androidTest/java.

7ad7d6bba44effcc.png

  1. Presiona las teclas Intro o Volver del teclado. El directorio androidTest ahora se puede ver en la pestaña Project.

bd0a1ed4d803e426.png

Del mismo modo que los directorios main y test tienen la misma estructura de paquete, el directorio androidTest debe contener esa misma estructura de paquete.

  1. Haz clic con el botón derecho en la carpeta androidTest/java y selecciona New > Package.
  2. En la ventana New Package, escribe com.example.tiptime.
  3. Presiona las teclas Intro o Volver del teclado. La estructura completa del paquete para el directorio androidTest ahora se puede ver en la pestaña Project.

Cómo crear la clase de prueba

En los proyectos de Android, el directorio de pruebas de instrumentación se designa como el directorio androidTest.

Para crear una prueba de instrumentación, debes repetir el mismo proceso que usaste para crear una prueba local, pero esta vez tienes que crearla dentro del directorio androidTest.

Crea la clase de prueba:

  1. Navega al directorio androidTest en el panel del proyecto.
  2. Haz clic en las flechas para expandir cf54f6c094aa8fa3.png que están junto a cada directorio hasta que veas el directorio tiptime.

14674cbab3cba3e2.png

  1. Haz clic con el botón derecho en el directorio tiptime y selecciona New > Kotlin Class/File.
  2. Ingresa TipUITests como el nombre de la clase.

acd0c385ae834a16.png

Cómo escribir la prueba

El código de prueba de instrumentación es bastante diferente del código de prueba local.

Las pruebas de instrumentación prueban una instancia real de la app y su IU, por lo que se debe configurar el contenido de la IU, de manera similar a como se configura el contenido en el método onCreate() del archivo MainActivity.kt cuando escribiste el código de la app de Tip Time. Debes hacerlo antes de escribir todas las pruebas de instrumentación para apps compiladas con Compose.

En el caso de las pruebas de la app de Tip Time, se escriben instrucciones para interactuar con los componentes de la IU para que se pruebe el proceso de cálculo de la propina a través de la IU. El concepto de prueba de instrumentación puede parecer abstracto al principio, pero no te preocupes. El proceso se describe en los siguientes pasos.

Escribe la prueba:

  1. Crea una variable composeTestRule establecida en el resultado del método createComposeRule() y anótala con la anotación Rule:
import androidx.compose.ui.test.junit4.createComposeRule
import org.junit.Rule

class TipUITests {

   @get:Rule
   val composeTestRule = createComposeRule()
}
  1. Crea un método calculate_20_percent_tip() y anótalo con la anotación @Test:
import org.junit.Test

@Test
fun calculate_20_percent_tip() {
}

El compilador sabe que los métodos con anotaciones @Test en el directorio androidTest se refieren a pruebas de instrumentación, mientras que los métodos con anotaciones @Test en el directorio test se refieren a pruebas locales.

  1. En el cuerpo de la función, llama a la función composeTestRule.setContent(). De esta manera, se configura el contenido de la IU de composeTestRule.
  2. En el cuerpo de lambda de la función, llama a la función TipTimeTheme()() con un cuerpo de lambda que llame a la función TipTimeLayout().
import com.example.tiptime.ui.theme.TipTimeTheme

@Test
fun calculate_20_percent_tip() {
    composeTestRule.setContent {
        TipTimeTheme {
           TipTimeLayout()
        }
    }
}

Cuando termines, el código debe ser similar al código escrito para establecer el contenido en el método onCreate() del archivo MainActivity.kt. Ahora que el contenido de la IU está configurado, puedes escribir instrucciones para interactuar con los componentes de la IU de la app. En esta app, debes probar que muestre el valor de propina correcto en función del importe de la factura y las entradas del porcentaje de propina.

  1. Se puede acceder a los componentes de IU como nodos a través de composeTestRule. Una forma común de hacerlo es acceder a un nodo que contiene un texto en particular con el método onNodeWithText(). En el importe de la factura, usa el método onNodeWithText() para acceder al elemento TextField componible:
import androidx.compose.ui.test.onNodeWithText

@Test
fun calculate_20_percent_tip() {
    composeTestRule.setContent {
        TipTimeTheme {
            TipTimeLayout()
        }
    }
    composeTestRule.onNodeWithText("Bill Amount")
}

A continuación, puedes llamar al método performTextInput() y pasar el texto que desees ingresar para completar el elemento TextField componible.

  1. Propaga el TextField del importe de la factura con un valor de 10:
import androidx.compose.ui.test.performTextInput

@Test
fun calculate_20_percent_tip() {
    composeTestRule.setContent {
        TipTimeTheme {
            TipTimeLayout()
        }
    }
    composeTestRule.onNodeWithText("Bill Amount")
.performTextInput("10")
}
  1. Usa el mismo enfoque para propagar el OutlinedTextField del porcentaje de propina con un valor 20:
@Test
fun calculate_20_percent_tip() {
    composeTestRule.setContent {
        TipTimeTheme {
            TipTimeLayout()
        }
    }
   composeTestRule.onNodeWithText("Bill Amount")
.performTextInput("10")
   composeTestRule.onNodeWithText("Tip Percentage").performTextInput("20")
}

Una vez que propagues todos los elementos TextField componibles, la propina se mostrará en un elemento Text componible en la parte inferior de la pantalla de la app.

Ahora que le indicaste a la prueba que propague estos elementos TextField componibles, debes asegurarte de que el elemento Text componible muestre la sugerencia correcta con una aserción.

En las pruebas de instrumentación con Compose, se puede llamar a las aserciones directamente en los componentes de la IU. Hay una cantidad de aserciones disponibles, pero en este caso deseas usar el método assertExists(). Se espera que se muestre el elemento Text componible y muestra el importe de la propina esperado: Tip Amount: $2.00.

  1. Realiza la aserción de que existe un nodo con ese texto:
import java.text.NumberFormat

@Test
fun calculate_20_percent_tip() {
    composeTestRule.setContent {
        TipTimeTheme {
            Surface (modifier = Modifier.fillMaxSize()){
                TipTimeLayout()
            }
        }
    }
   composeTestRule.onNodeWithText("Bill Amount")
      .performTextInput("10")
   composeTestRule.onNodeWithText("Tip Percentage").performTextInput("20")
   val expectedTip = NumberFormat.getCurrencyInstance().format(2)
   composeTestRule.onNodeWithText("Tip Amount: $expectedTip").assertExists(
      "No node with this text was found."
   )
}

Ejecuta la prueba

El proceso para ejecutar una prueba de instrumentación es el mismo que se usa con una prueba local. Haz clic en las flechas del margen junto a cada declaración para ejecutar una prueba individual o toda la clase de prueba.

ad45b3e8730f9bf2.png

  • Haz clic en las flechas que se encuentran junto a la declaración de la clase. Puedes ver las pruebas que se ejecutan en tu dispositivo o emulador. Cuando finalice la prueba, deberías ver el resultado que se muestra en la siguiente imagen:

bfd75ec0a8a98999.png

6. Obtén el código de la solución

Como alternativa, puedes clonar el repositorio de GitHub para el código:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git
$ cd basic-android-kotlin-compose-training-tip-calculator
$ git checkout test_solution

7. Conclusión

¡Felicitaciones! Escribiste tus primeras pruebas automatizadas en Android. Las pruebas son un componente fundamental del control de calidad del software. A medida que sigas compilando apps para Android, asegúrate de escribir pruebas junto con las funciones de la app a fin de garantizar que estas funcionen correctamente durante el proceso de desarrollo.

Resumen

  • Qué son las pruebas automatizadas
  • Por qué son importantes las pruebas automatizadas
  • Diferencia entre las pruebas locales y las de instrumentación
  • Prácticas recomendadas básicas para escribir pruebas automatizadas
  • Dónde encontrar y ubicar clases locales de prueba de instrumentación en un proyecto de Android
  • Cómo crear un método de prueba
  • Cómo crear clases de prueba locales y de instrumentación
  • Cómo realizar aserciones en pruebas locales y de instrumentación
  • Cómo usar las reglas de prueba
  • Cómo usar ComposeTestRule para iniciar la app con una prueba
  • Cómo interactuar con elementos componibles en una prueba de instrumentación
  • Cómo ejecutar pruebas