Configuración del Proyecto

Clase 3 de 16Curso de Android Testing

Resumen

La implementación de pruebas en aplicaciones Android es fundamental para garantizar la calidad y estabilidad del software. Dominar las técnicas de testing te permitirá desarrollar aplicaciones más robustas y detectar errores antes de que lleguen a producción. En este recorrido, exploraremos un proyecto estructurado que servirá como base para aplicar diferentes estrategias de testing en Android.

¿Cómo está estructurado el proyecto de testing?

El proyecto sobre el cual trabajaremos sigue una arquitectura por capas, similar a la que encontrarías en un entorno profesional. Esta estructura facilita la aplicación de diferentes tipos de pruebas según la capa que queramos evaluar.

La organización principal del proyecto incluye tres carpetas fundamentales:

  • Data: Contiene la implementación de acceso a datos y APIs
  • Domain: Alberga las clases de modelo y contratos de repositorios
  • Presentation: Incluye los componentes de UI y ViewModels

Explorando la capa de datos

En la capa de datos encontramos una implementación que simula llamadas a una API externa mediante Retrofit. La interfaz UserAPI define tres endpoints principales:

// Ejemplo similar a la interfaz UserAPI
interface UserAPI {
    fun getUser(userId: String): User
    fun getPlaces(userId: String): List<Place>
    fun getProfile(userId: String): Profile
}

La implementación del repositorio (UserRepositoryImplementation) muestra cómo se manejan estas llamadas utilizando coroutines y builders para gestionar los resultados. Este componente es crucial ya que:

  • Implementa llamadas asíncronas usando coroutines
  • Maneja errores y resultados exitosos
  • Combina múltiples llamadas para construir objetos complejos

Por ejemplo, el método fetchUser obtiene información del usuario, mientras que getPlaces recupera los lugares asociados a ese usuario. Estos datos se combinan para construir un perfil completo.

Entendiendo la capa de dominio

La capa de dominio contiene las clases de datos que representan nuestro modelo de negocio:

// Ejemplos de clases de dominio
data class Place(val id: String, val name: String, val coordinates: Coordinates)
data class Coordinates(val latitude: Double, val longitude: Double)
data class User(val id: String, val userName: String)
data class Profile(val user: User, val places: List<Place>)

También encontramos la interfaz UserRepository que define el contrato que debe implementar cualquier repositorio de usuarios, siguiendo el principio de inversión de dependencias.

Analizando la capa de presentación

En la capa de presentación tenemos:

  • ProfileViewModel: Recibe un ID de usuario y actualiza el estado de la UI según los resultados obtenidos del repositorio
  • ProfileScreen: Un composable que muestra diferentes estados (carga, error o contenido) según la información recibida del ViewModel

¿Qué tipos de pruebas implementaremos?

El proyecto está configurado para soportar diferentes tipos de pruebas, siguiendo la pirámide de testing:

Pruebas unitarias

Las pruebas unitarias se almacenan en la carpeta src/test. Estas pruebas evalúan componentes individuales de forma aislada y son fundamentales para verificar la lógica de negocio.

Pruebas de instrumentación

Las pruebas de instrumentación (o pruebas de integración) se ubican en la carpeta src/androidTest. Estas pruebas requieren un dispositivo o emulador Android y evalúan la interacción entre componentes o con el sistema Android.

¿Qué librerías utilizaremos para testing?

El proyecto incorpora varias librerías especializadas para testing:

  • JUnit: Framework base para pruebas unitarias
  • Compose UI Testing: Librerías para probar composables de Jetpack Compose
  • Mockk: Librería para crear mocks en Kotlin, esencial para simular dependencias
  • MockWebServer: Permite simular respuestas de servidores para probar llamadas de red
  • Kotlin Coroutines Test: Proporciona utilidades para probar código con coroutines
  • Turbine: Facilita las pruebas de Kotlin Flows
  • Truth: Framework de aserciones con sintaxis más legible y mensajes de error más claros

La configuración del archivo build.gradle asegura que estas dependencias estén disponibles en los contextos adecuados:

// Dependencias para pruebas unitarias
testImplementation "junit:junit:$junit_version"
testImplementation "com.google.truth:truth:$truth_version"
testImplementation "io.mockk:mockk:$mockk_version"
testImplementation "com.squareup.okhttp3:mockwebserver:$mockwebserver_version"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
testImplementation "app.cash.turbine:turbine:$turbine_version"

// Dependencias para pruebas de instrumentación
androidTestImplementation "androidx.test.ext:junit:$androidx_junit_version"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"

Es importante destacar la configuración packagingOptions en el bloque android del archivo build.gradle, que es necesaria para resolver conflictos de archivos durante la ejecución de pruebas.

¿Cómo comenzar con las pruebas unitarias?

Para implementar pruebas efectivas, es fundamental comprender la estructura del proyecto y las responsabilidades de cada componente. En las próximas secciones, comenzaremos a escribir pruebas unitarias para verificar el comportamiento de nuestros componentes de forma aislada.

Cada archivo del proyecto tiene un propósito específico que iremos descubriendo a medida que implementemos diferentes estrategias de testing. La combinación de estas técnicas nos permitirá construir una suite de pruebas robusta que garantice la calidad de nuestra aplicación.

Te invito a explorar más sobre las librerías mencionadas y compartir en los comentarios cualquier dato interesante que encuentres sobre ellas. En la próxima sección, nos sumergiremos en la implementación de nuestra primera prueba unitaria.