Cómo escribir pruebas de unidades

1. Antes de comenzar

En codelabs anteriores, aprendiste a crear un proyecto con Android Studio, modificar XML con el fin de crear una IU personalizada para tu app y modificar la lógica empresarial a los efectos de agregar funcionalidad. Este codelab se enfoca en la importancia de las pruebas y amplía las pruebas de unidades. Tendrás la oportunidad de ver su aspecto y la forma de escribirlas.

Requisitos previos

  • Haber creado un proyecto en Android Studio
  • Tener cierta experiencia en la escritura de código en Android Studio

Qué aprenderás

  • Por qué son importantes las pruebas
  • Cómo se ven las pruebas de unidades
  • Cómo escribir y ejecutar las pruebas de unidades

Requisitos

  • Una computadora que tenga Android Studio instalado
  • El proyecto que creaste en el codelab anterior de esta ruta de aprendizaje

2. Introducción

Ahora que escribiste código de Android, es un buen momento para seguir con algún código de prueba. En primer lugar, repasarás la filosofía de las pruebas y, luego, profundizarás en las pruebas generadas automáticamente en un proyecto de Android. Por último, escribirás tus propias pruebas para la app de Dice Roller. Esta lección incluye mucho material, pero no te preocupes. Tómate tu tiempo a la hora de utilizarlo, ya que las pruebas requieren dedicación y mucha práctica para aprender. No te desanimes si no lo comprendes de inmediato.

¿Por qué son importantes las pruebas?

Al principio, puede parecer que no necesitas hacer pruebas en tu app. Cuando esta es pequeña y tiene una funcionalidad limitada, es fácil probarla de forma manual y determinar si todo funciona correctamente. Sin embargo, a medida que la app crece, las pruebas manuales requieren mucho más esfuerzo que escribir pruebas automatizadas. Además, una vez que comienzas a trabajar en apps de nivel profesional, las pruebas se vuelven esenciales cuando tienes una base de usuarios grande. 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.

Un análisis más detallado de las pruebas de unidades

En este codelab, te enfocarás en las pruebas de unidades. Más adelante, abordarás las pruebas de instrumentación. Para comenzar, observa las pruebas que se generan cuando creas una app para Android a través de Android Studio. También adquirirás experiencia práctica en la ejecución de pruebas y te familiarizarás con la escritura de código de prueba.

En una ruta de aprendizaje anterior, revisaste la ubicación de los archivos de origen con el fin de realizar pruebas. Las pruebas de unidades siempre se encuentran en el directorio test:

f02b380da4e8f661.png

  1. Abre el archivo app/build.gradle y observa las dependencias. Verás algunas marcadas como testImplementation y androidTestImplementation, que corresponden a las pruebas de instrumentación y de unidades, respectivamente. Ten en cuenta lo siguiente:

app/build.gradle

testImplementation 'junit:junit:4.12'

La biblioteca JUnit que controla las pruebas de unidades y te permite marcar el código como una prueba, de modo que se pueda compilar y ejecutar para probar el código de la app

  1. En el directorio test, abre el archivo ExampleUnitTest.kt.

Deberías ver una prueba de unidades como ejemplo que se ve de la siguiente manera:

ExampleUnitTest.kt

class ExampleUnitTest {
   @Test
   fun addition_isCorrect() {
       assertEquals(4, 2 + 2)
   }
}

Si bien agregaste código a la app de Dice Roller, es probable que no hayas escrito ninguna prueba. Por lo tanto, solo hay código genérico que Android Studio crea automáticamente. Esta es una prueba arbitraria que funciona como marcador de posición para pruebas más relevantes que se espera que el desarrollador escriba. Actualmente, este bloque de código solo prueba que 2 + 2 = 4. Por supuesto, siempre es verdadero. Analiza con más detalle lo que sucede:

  • Primero, las funciones de prueba deben tener la anotación @ Test importada de la biblioteca org.junit.test. Puedes considerar la anotación como etiquetas de metadatos para un fragmento de código que puede cambiar la forma en que se compila el código. En este caso, la anotación @Test permite que el compilador sepa que el siguiente método es una prueba, lo que le permite ejecutarse como tal.

Después de la anotación, tienes una declaración de función, en este caso la función addition_isCorrect(). Dentro de ella, la función assertEquals() declara que un valor esperado debe ser igual a un valor real obtenido a través de lógica empresarial. Los métodos de aserción son el objetivo final de una prueba de unidades. En última instancia, debes declarar que un resultado obtenido de tu código se encuentra en un estado particular. Si el estado del resultado coincide con el estado esperado, se pasa la prueba. Si el estado del resultado no coincide con el estado esperado, la prueba falla. En este caso, el código compara dos valores, por lo que el método assertEquals() toma dos parámetros: un valor esperado y un valor real. Tal como indica su nombre, el valor esperado es el resultado específico que esperas, en este caso, 4. El valor real representa el resultado de un fragmento de código real. En general, esto probaría un fragmento de código de la propia app. En este caso, solo es un fragmento de código arbitrario, por ejemplo, 2 + 2. Sin más que agregar, ejecuta esta prueba para ver qué sucede.

Existen varias maneras de ejecutar pruebas en Android Studio, que luego analizarás en mayor detalle. Por el momento, concéntrate en lo simple.

  1. Junto a la declaración del método addition_isCorrect, haz clic en las flechas y, luego, selecciona Run 'ExampleUnitTest.addition_isCorrect'.

78c943e851a33644.png

Esto es lo que se conoce como prueba positiva. En otras palabras, la aserción es afirmativa. 2 + 2 es igual a 4. También podríamos escribir una prueba negativa que realice una aserción en el negativo. Por ejemplo, 2 + 2 no es igual a 5.

En el panel Run, deberías ver algo como esta captura de pantalla:

190df0c8ff787233.png

Hay varios indicios de que la prueba se realizó correctamente, por ejemplo, marcas verdes y la cantidad de pruebas que se pasaron.

aa7d361d8e4826ef.png

  1. Modifica la prueba para ver cómo se ve una falla. Cambia 2 + 2 a 2 + 3 y, luego, vuelve a ejecutar la prueba. Ten en cuenta que solo estás experimentando con el código generado a fin de adquirir experiencia sobre el funcionamiento de las pruebas. Estos cambios no son relevantes para la funcionalidad de Dice Roller.

ExampleUnitTest.kt

class ExampleUnitTest {
   @Test
   fun addition_isCorrect() {
       assertEquals(4, 2 + 3)
   }
}

Después de ejecutar el resto, deberías ver algo como esta captura de pantalla:

751ac8089cf4c47c.png

El texto en color rojo indica una prueba fallida. Si haces clic en los elementos del menú de los resultados de la prueba, verás un mensaje de error que indica el motivo por el que falló la prueba.

163708373e651ecc.png

En este caso, el mensaje indica que la aserción falló porque esperaba un resultado de 4, pero el valor real fue 5. Esto tiene sentido porque cambiaste el valor real de modo que sea 2 + 3, pero dejaste el valor esperado como 4. También puedes ver la línea en la que falló la prueba. En este caso, se trata de la línea 15, denotada como ExampleUnitTest.kt:15.

  1. A fines de ser más detallista, cambia el valor esperado de 4 a 5 y vuelve a ejecutar la prueba. Esta ahora debería ser exitosa, ya que el valor esperado coincide con el resultado real del código en cuestión.

3. Escribe tu primera prueba de unidades

Ahora que adquiriste cierta comodidad en torno a las pruebas de unidades, puedes escribir la tuya, una que sea más relevante para la app de Dice Roller.

Como ya notaste, la funcionalidad principal de la app de Dice Roller se basa en un generador de números al azar. Lamentablemente, estos generadores son muy difíciles de probar, ya que no puedes saber con certeza el resultado de una cantidad generada de forma aleatoria. El objetivo de esta prueba es garantizar que cuando lances el dado o llames al método roll en la clase dice, se te devuelva un número apropiado. La prueba que escribes simplemente comprueba que el resultado del generador de números al azar sea un número dentro del rango que especificaste para el generador.

  1. En el archivo ExampleUnitTest.kt, borra el método de prueba generado y las instrucciones de importación. Tu archivo ahora debería tener el siguiente aspecto:

c06e8b402f293b5e.png

  1. Crea una función generates_number():

ExampleUnitTest.kt

fun generates_number() {
}
  1. Anota el método generates_number() con la anotación @Test. Ten en cuenta que, cuando intentas llamar a @Test, el texto es de color rojo. Esto se debe a que no puede encontrar la declaración de esta anotación, por lo que deberás importarla. Puedes hacerlo automáticamente si presionas Control+Enter (o Options+Return en Mac).

Si haces clic en la línea de código, deberías ver un mensaje para importar:

bbe5791b9565588c.png

De manera alternativa, también puedes copiar y pegar el archivo import org.junit.Test después del nombre del paquete y antes de la declaración de la clase. El código debería verse de la siguiente manera:

9a94c2bdf84adb61.png

  1. Crea una instancia del objeto Dice.

ExampleUnitTest.kt

@Test
fun generates_number() {
   val dice = Dice(6)
}
  1. A continuación, llama al método roll() en esta instancia y almacena el valor que se muestra.

ExampleUnitTest.kt

@Test
fun generates_number() {
   val dice = Dice(6)
   val rollResult = dice.roll()
}
  1. Por último, haz una aserción real. En otras palabras, debes declarar que el método mostró un valor que está dentro de la cantidad de caras que pasaste. Por lo tanto, en este caso, el valor debe ser mayor a 0 y menor a 7. Para lograr esto, usa el método assertTrue(). Ten en cuenta que, cuando intentas llamar al método assertTrue(), el texto es de color rojo al principio. Esto se debe a que no puede encontrar la declaración de este método, por lo que deberás importarlo, de manera similar a lo que ocurrió con la anotación.

10eea07fc21bf998.png

Puedes importarlo automáticamente como se explicó con anterioridad. Sin embargo, ten en cuenta que esta vez tienes varias opciones para elegir. En este caso, debería ser la opción del paquete org.junit.Assert:

5dbfba2ba0e37ac9.png

Como alternativa, puedes pegar este código después de la sentencia de importación para la anotación de prueba:

ExampleUnitTest.kt

import org.junit.Assert.assertTrue

El código ahora tiene el siguiente aspecto:

347f792f455ae6b5.png

Si colocas el cursor entre los paréntesis y presionas Control+P (o Command+P en Mac), verás información sobre la herramienta que muestra los parámetros que toma el método:

865cf0ac47738e08.png

El método assertTrue() toma dos parámetros: una String y un Boolean. Si la aserción falla, la string es un mensaje que se muestra en la consola. El valor booleano es una sentencia condicional. Establece el mensaje en lo siguiente:

ExampleUnitTest.kt

"The value of rollResult was not between 1 and 6"

Como se mencionó con anterioridad, probar números aleatorios es un desafío, ya que el valor del número no se puede predecir debido a su naturaleza aleatoria. Lo único que se puede hacer es garantizar que el valor esté dentro de un rango particular. Establece el parámetro de condición en lo siguiente:

ExampleUnitTest.kt

rollResult in 1..6

El código debería verse de la siguiente manera:

ExampleUnitTest.kt

@Test
fun generates_number() {
   val dice = Dice(6)
   val rollResult = dice.roll()
   assertTrue("The value of rollResult was not between 1 and 6", rollResult in 1..6)
}
  1. Haz clic en las flechas que aparecen junto a la función y selecciona Run 'ExampleUnitTest.generates_number()'.

Si el código tiene el aspecto del fragmento de código anterior, la prueba debería ser exitosa.

  1. Opcional: Si quieres practicar aún más, modifica el dado para que tenga 4 o 5 caras sin cambiar la aserción a fin de que falle la prueba.

4. Felicitaciones

Aprendiste lo siguiente:

  • La importancia de las pruebas
  • El aspecto de una prueba de unidades
  • La forma de ejecutar una prueba de unidades
  • Algunas sintaxis de prueba comunes
  • La modo de escribir una prueba de unidades

Más información