Cómo probar las solicitudes de la red

1. Antes de comenzar

En codelabs anteriores, aprendiste a realizar solicitudes de red con Retrofit. En este codelab, aprenderás a escribir pruebas de unidades para tu código de red.

Requisitos previos

  • Haber creado directorios de prueba en Android Studio
  • Haber escrito pruebas de instrumentación y de unidades en Android Studio
  • Haber agregado dependencias de Gradle a un proyecto de Android

Qué aprenderás

  • Cómo almacenar recursos para pruebas
  • Cómo simular respuestas de la API para pruebas
  • Cómo probar los servicios de la API de Retrofit

Requisitos

  • Una computadora que tenga Android Studio instalado
  • El código de la solución de la app de Mars Photos

Descarga el código de partida para este codelab

En este codelab, agregarás pruebas de instrumentación a la app de Mars Photos a partir del código de la solución anterior.

Para obtener el código necesario para este codelab y abrirlo en Android Studio, haz lo siguiente:

Obtén el código

  1. Haz clic en la URL proporcionada. Se abrirá la página de GitHub del proyecto en un navegador.
  2. En esa página, haz clic en el botón Code, que abre un cuadro de diálogo.

5b0a76c50478a73f.png

  1. En el cuadro de diálogo, haz clic en el botón Download ZIP para guardar el proyecto en tu computadora. Espera a que se complete la descarga.
  2. Ubica el archivo en tu computadora (probablemente en la carpeta Descargas).
  3. Haz doble clic en el archivo ZIP para descomprimirlo. Se creará una carpeta nueva con los archivos del proyecto.

Abre el proyecto en Android Studio

  1. Inicia Android Studio.
  2. En la ventana Welcome to Android Studio, haz clic en Open an existing Android Studio project.

36cc44fcf0f89a1d.png

Nota: Si Android Studio ya está abierto, selecciona la opción de menú File > New > Import Project.

21f3eec988dcfbe9.png

  1. En el cuadro de diálogo Import Project, navega hasta donde se encuentra la carpeta de proyecto descomprimido (probablemente en Descargas).
  2. Haz doble clic en la carpeta del proyecto.
  3. Espera a que Android Studio abra el proyecto.
  4. Haz clic en el botón Run 11c34fc5e516fb1c.png para compilar y ejecutar la app. Asegúrate de que funcione como se espera.
  5. Explora los archivos del proyecto en la ventana de herramientas Project para ver cómo se configuró la app.

2. Descripción general de la app de partida

La app de Mars Photos consta de una sola pantalla que muestra una lista de fotos recuperadas mediante una solicitud de red.

Este código de partida tiene algunas variaciones adicionales relacionadas con las pruebas. No profundizaremos acerca de ellas porque no se incluyen en este codelab, pero resulta importante mencionarlas.

Cuando se prueba cómo una app controla la recuperación de datos desde una API, siempre es mejor proporcionar datos propios para poder comprobar el modo en que deberían verse. Los datos de la API pueden cambiar, lo que puede interrumpir nuestras pruebas y hacer que fallen de forma innecesaria. Además, si nos basamos en una llamada de red real, se podrían generar fallas según la conectividad o la velocidad de la red, lo que puede hacer que nuestras pruebas no sean coherentes. En consecuencia, generaremos algunos datos en nuestras pruebas y los almacenaremos como un archivo JSON en el directorio test/res. Este es un directorio de recursos, similar al directorio de recursos en el código principal de la app, con la diferencia de que nuestros recursos de prueba se almacenan en test/res.

  1. Agrega el directorio test/res. Haz clic con el botón derecho en el directorio src de la vista de proyecto y selecciona New -> Directory en el menú desplegable.

95e789a6a8d34185.png

  1. En la ventana emergente que aparece, desplázate hacia abajo y selecciona test/res.

eeb4ef7904e60846.png

  1. En la vista de proyectos, haz clic con el botón derecho en el directorio test/res y selecciona New -> File.

6ea89527d6534c1a.png

  1. Asígnale el nombre mars_photos.json al archivo.

c4f463255956ce33.png

  1. Copia el siguiente código en el archivo mars_photos.json:
[
 {
   "id":"424905",
   "img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000MR0044631300503690E01_DXXX.jpg"
 },
 {
   "id":"424906",
   "img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"
 }
]
  1. Para poder acceder a los archivos de este directorio de recursos, el directorio debe especificarse de manera explícita como directorio del "código fuente" en el archivo de compilación. Agrega la siguiente línea:

app/build.gradle

android {
    ...
    sourceSets {
       test.resources.srcDirs += 'src/test/res'
    }
}

Esto nos permite acceder a nuestros archivos de recursos sin tener que escribir la ruta completa al archivo en nuestro código cada vez que se accede a él en una prueba. No se recomienda escribir la ruta de acceso completa para realizar pruebas, ya que estas rutas de acceso de archivos pueden cambiar entre equipos y sistemas operativos.

  1. En el mismo archivo, agrega las siguientes dependencias y sincroniza Gradle.

app/build.gradle

dependencies {
    ...
    testImplementation 'junit:junit:4.12'
    testImplementation "androidx.arch.core:core-testing:2.1.0"
    testImplementation "com.squareup.okhttp3:mockwebserver:4.9.1"
}
  1. Ahora, crea un directorio de prueba normal.

22137bac57bd2d77.png

  1. En el directorio de prueba, crea un paquete nuevo com.example.android.marsphotos.
  2. En el paquete, crea un nuevo archivo Kotlin llamado BaseTest.kt.

481f7a26f0935093.png

  1. Copia y pega el siguiente código en el archivo BaseTest.kt. Si no conoces el código, no te preocupes. El término "Mock" aparece repetidamente en el siguiente código y se tratará durante este codelab. La función enqueue() de esta clase analiza los datos del archivo mars_photos.json que creaste para que se pueda usar en una prueba que escribirás más adelante.

BaseTest.kt

import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okio.buffer
import okio.source

open class BaseTest {

   val mockWebServer = MockWebServer()

   fun enqueue(fileName: String) {
       val inputStream = javaClass.classLoader!!.getResourceAsStream(fileName)
       val buffer = inputStream.source().buffer()

       mockWebServer.enqueue(
           MockResponse()
               .setResponseCode(200)
               .setBody(buffer.readString(Charsets.UTF_8))
       )
   }
}

3. Simulación de datos

En este codelab, dependemos en gran medida de la simulación de datos. En el contexto de una prueba, simulación significa que estamos simulando el valor devuelto de un fragmento de código. En un codelab anterior, simulamos una clase. También podemos simular funciones y hacer que se muestren valores específicos o simular una API para que muestre datos específicos. Esto es útil para realizar pruebas, ya que nos ayuda a aislar un fragmento de código y probarlo. En este codelab, nos enfocamos en simular una respuesta de la API. Explicaremos las funciones ficticias en un codelab independiente.

4. Crea una clase de prueba de unidades

En esta prueba, probaremos el servicio de la API. Primero, crea una nueva clase llamada MarsApiServiceTests.kt.

5. Dependencias

El código de partida de este codelab incluye las dependencias que necesitas. Sin embargo, existe una dependencia importante que aún no tratamos.

testImplementation "com.squareup.okhttp3:mockwebserver:4.9.1"

Esta dependencia nos permite crear un servidor simulado. En esencia, el servidor simulado intercepta las solicitudes de red y las redirige para mostrar los datos simulados que definimos. Más adelante en este codelab, analizaremos qué significa la simulación.

6. Prácticas recomendadas

Ten en cuenta que nuestras pruebas se escriben en Kotlin, un lenguaje de programación orientado a objetos. Esto significa que podemos escribir código orientado a objetos para nuestras pruebas. Con la cantidad de código que escribiremos para esta prueba, no es necesario utilizar prácticas orientadas a objetos. Sin embargo, lo implementaremos para demostrar el concepto.

Tal vez notaste que el código de partida ya incluía un directorio de prueba de unidades y había una clase llamada BaseTest. Si tenemos un fragmento de código que podemos usar para varias pruebas, podemos crear un open class y heredar nuestras clases de prueba. Ten en cuenta que esta clase BaseTest solo tiene un método llamado enqueue() y que solo escribiremos una clase de prueba en este codelab. Cuando escribas tus propias pruebas, recuerda que puedes aprovechar la programación orientada a objetos. Sin embargo, es importante no usarla demasiado cuando no sea necesario.

7. Escribe una prueba de solicitud de red

Primero, asegúrate de que tu clase de prueba se herede de BaseTest.

Probaremos directamente el objeto MarsApiService, por lo que necesitaremos una instancia de este. La configuraremos de la misma manera que lo hicimos en el código de la app, pero la URL de este nuevo servicio será diferente.

  1. En tu clase de prueba, crea una variable lateinit para MarsApiService.
private lateinit var service: MarsApiService

Ahora, necesitamos una función que defina la variable service antes de cada prueba.

  1. Crea una función llamada setup() y anótala con @Before.
@Before
fun setup() {}

En esta prueba, no utilizaremos una URL para nuestras solicitudes de red. Aquí es donde MockWebServer resulta útil.

Simulación de datos

En la clase BaseTest, hay una propiedad llamada mockWebServer,, que es simplemente una instancia del objeto MockWebServer. Este objeto interceptará nuestras solicitudes de red, pero primero debemos dirigir nuestras solicitudes de red a la URL que se interceptará.

MockWebServer tiene una función llamada url() que especifica la URL que quieres interceptar. Recuerda que no queremos hacer una solicitud de red real, solo queremos simular la creación de una para poder probar el código de red con datos que controlamos en la prueba. La función url() toma una cadena que representa esa URL falsa y muestra un objeto HttpUrl. En la función setup(), escribe la siguiente línea:

val url = mockWebServer.url("/")

Configuramos el extremo de URL que queremos interceptar y capturamos el objeto HttpUrl que se muestra.

Dentro de esta función, crea una instancia de MarsApiService de la misma manera que se hizo en MarsApiService y MarsApiClass (excluyendo la variable lazy). Sin embargo, todavía no incluyas una URL base. Debería ser similar a lo siguiente:

service = Retrofit.Builder()
   .addConverterFactory(MoshiConverterFactory.create(
       Moshi.Builder()
           .add(KotlinJsonAdapterFactory())
           .build()
   ))
   .build()
   .create(MarsApiService::class.java)

Ahora, es el momento de establecer la URL base. Agrega lo siguiente a la cadena de funciones de Retrofit.Builder():

.baseUrl(url)

Esto especifica a nuestro servicio de la API que queremos orientar las solicitudes a nuestro MockWebServer.

service = Retrofit.Builder()
   .baseUrl(url)
   .addConverterFactory(MoshiConverterFactory.create(
       Moshi.Builder()
           .add(KotlinJsonAdapterFactory())
           .build()
   ))
   .build()
   .create(MarsApiService::class.java)

Una vez más, el punto de MockWebServer es evitar realizar una solicitud de red real a una API real. La idea básica es que si se realiza una solicitud de red real, la prueba falla si la API falla. El uso de una API real prueba la API en sí y solo nos preocupa probar el código en nuestro proyecto de Android.

Puedes pensar en MockWebServer como una API falsa que muestra datos que nosotros creamos y, por lo tanto, necesitamos indicarle explícitamente a MockWebServer lo que debe mostrar antes de realizar una solicitud. Aquí es donde entra en juego la función enqueue() en BaseTest. No te preocupes demasiado por el código en esta función, ya que no se incluye en este codelab. Solo ten en cuenta que la función toma un archivo de nuestros recursos de prueba y lo convierte en una respuesta de API falsa.

  1. Crea una función de prueba llamada api_service(). En tu función de prueba, llama al método enqueue de la siguiente manera:
enqueue("mars_photos.json")
  1. A continuación, llamaremos a la función getPhotos() directamente desde MarsApiService. Recuerda que getPhotos() es una función de suspensión y debe llamarse desde un alcance de corrutinas. Para ello, une nuestra llamada al método en runBlocking de la siguiente manera:
runBlocking {
    val apiResponse = service.getPhotos()
}
  1. Ahora, asegurémonos de que nuestra respuesta de getPhotos() no sea null. Recuerda que definimos apiResponse dentro de runBlocking, por lo que también debemos acceder a él dentro de runBlocking.
runBlocking {
    val apiResponse = service.getPhotos()
    assertNotNull(apiResponse)
}

getPhotos() muestra una lista que contiene objetos MarsPhoto, así que asegurémonos de que la lista tenga el tamaño esperado.

  1. Mira el archivo mars_photos.json en test/res. Crea otra aserción para asegurarte de que la lista no esté vacía. También crearemos una aserción para verificar que algunos de los datos sean correctos. Ve a test/res/mars_photos.json y copia uno de los IDs. Confirma que el valor de ese ID es igual al valor del ID del elemento de lista correspondiente.
runBlocking {
   val apiResponse = service.getPhotos()

   assertNotNull(apiResponse)
   assertTrue("The list was empty", apiResponse.isNotEmpty())
   assertEquals("The IDs did not match", "424905", apiResponse[0].id)
}

Tu código debería verse de la siguiente manera: 4000883c0d6d47bb.png

8. Código de solución

9. Felicitaciones

Probar las solicitudes de red puede ser muy complicado y, en este codelab, solo se trata el tema superficialmente. Cada prueba de red que escribas será única para las APIs de las que tu app obtenga datos. A medida que ganes más experiencia cuando trabajes con las APIs, podrás expandir tus pruebas para simular fallas en la red y diferentes respuestas de la API. Esta puede ser un área de prueba muy desafiante. Para llegar a tener experiencia, se necesitan pruebas y errores, así que asegúrate de seguir practicando.

En este codelab, aprendimos lo siguiente:

  • Cómo aplicar la programación orientada a objetos a las pruebas
  • Cómo almacenar recursos en un directorio de prueba
  • Cómo llamar a las funciones de suspensión en una prueba
  • Cómo simular respuestas de la API en una prueba