Cómo usar pruebas dobles en Android

Cuando pruebas un elemento o un sistema de elementos, lo haces de forma aislada. Por ejemplo, para probar un ViewModel, no necesitas iniciar un emulador ni iniciar una IU, ya que no depende del framework de Android (o no debería).

Sin embargo, es posible que el sujeto de prueba dependa de otros para que funcione. Por ejemplo, un ViewModel puede depender de un repositorio de datos para funcionar.

Cuando necesitas proporcionar una dependencia a un sujeto de prueba, una práctica común es crear un doble de prueba (o objeto de prueba). Los dobles de prueba son objetos que se ven y actúan como componentes en tu app, pero se crean en la prueba para proporcionar un comportamiento o datos específicos. La ventaja principal es que hacen que tus pruebas sean más rápidas y sencillas.

Tipos de dobles de prueba

Existen varios tipos de dobles de prueba:

Falso Un doble de prueba que tiene una implementación "funcionante" de la clase, pero que se implementa de una manera que lo hace adecuado para las pruebas, pero no para la producción.

Ejemplo: una base de datos en memoria.

Los fakes no requieren un framework de simulación y son ligeros. Son preferentes.

Simulación Un doble de prueba que se comporta como lo programaste y que tiene expectativas sobre sus interacciones. Las simulaciones fallarán en las pruebas si sus interacciones no coinciden con los requisitos que definas. Por lo general, las simulaciones se crean con un framework de simulación para lograr todo esto.

Ejemplo: Verifica que se haya llamado a un método en una base de datos exactamente una vez.

Stub Es un doble de prueba que comporta la forma en que lo programas para que se comporte, pero no tiene expectativas sobre sus interacciones. Por lo general, se crea con un framework de simulación. Por cuestiones de simplicidad, se prefieren las falsificaciones en lugar de los stubs.
Cuenta ficticia Es un doble de prueba que se pasa, pero no se usa, por ejemplo, si solo necesitas proporcionarlo como parámetro.

Ejemplo: Una función vacía pasada como devolución de llamada de clic.

Espía Wrapper sobre un objeto real que también realiza un seguimiento de información adicional, similar a las simulaciones. Por lo general, se evitan porque agregan complejidad. Por lo tanto, se prefieren las simulaciones o las falsificaciones en lugar de los espías.
Sombra Se usa un objeto falso en Robolectric.

Ejemplo de uso de un elemento falso

Supongamos que deseas realizar pruebas de unidad de un ViewModel que depende de una interfaz llamada UserRepository y que expone el nombre del primer usuario a una IU. Para crear un doble de prueba falso, implementa la interfaz y muestra datos conocidos.

object FakeUserRepository : UserRepository {
    fun getUsers() = listOf(UserAlice, UserBob)
}

val const UserAlice = User("Alice")
val const UserBob = User("Bob")

Este UserRepository falso no necesita depender de las fuentes de datos locales y remotas que usaría la versión de producción. El archivo se encuentra en el conjunto de fuentes de prueba y no se enviará con la app de producción.

Una dependencia falsa puede mostrar datos conocidos sin depender de fuentes de datos remotas.
Figura 1: Una dependencia falsa en una prueba de unidad.

La siguiente prueba verifica que ViewModel exponga correctamente el primer nombre de usuario a la vista.

@Test
fun viewModelA_loadsUsers_showsFirstUser() {
    // Given a VM using fake data
    val viewModel = ViewModelA(FakeUserRepository) // Kicks off data load on init

    // Verify that the exposed data is correct
    assertEquals(viewModel.firstUserName, UserAlice.name)
}

Reemplazar UserRepository por una instancia falsa es fácil en una prueba de unidades, ya que el verificador crea el ViewModel. Sin embargo, puede ser un desafío reemplazar elementos arbitrarios en pruebas más grandes.

Reemplazo de componentes y la inserción de dependencias

Cuando las pruebas no tienen control sobre la creación de los sistemas a prueba, reemplazar los componentes por dobles de prueba se vuelve más complejo y requiere que la arquitectura de tu app siga un diseño probable.

Incluso las pruebas de extremo a extremo grandes pueden beneficiarse del uso de dobles de prueba, como una prueba de IU instrumentada que navega por un flujo de usuarios completo en tu app. En ese caso, te recomendamos que hagas que tu prueba sea hermética. Una prueba hermética evita todas las dependencias externas, como la recuperación de datos de Internet. Esto mejora la confiabilidad y el rendimiento.

Figura 2: Una gran prueba que abarca la mayor parte de la app y falsifica los datos remotos.

Puedes diseñar tu app para lograr esta flexibilidad de forma manual, pero te recomendamos que uses un framework de inyección de dependencia, como Hilt, para reemplazar los componentes de tu app durante la prueba. Consulta la guía de pruebas de Hilt.

Próximos pasos

En la página Estrategias de pruebas, se muestra cómo puedes mejorar tu productividad con diferentes tipos de pruebas.