Cómo usar pruebas dobles en Android

Cuando se diseña la estrategia de prueba de un elemento o sistema, hay tres aspectos de prueba relacionados:

  • Alcance: ¿Qué parte del código abarca la prueba? Las pruebas pueden verificar un solo de una aplicación, toda la aplicación o algún punto intermedio. El el alcance probado está en prueba y comúnmente se refiere a él como el Sujeto Prueba, también el Sistema bajo prueba o la Unidad en prueba.
  • Velocidad: ¿Qué tan rápido se ejecuta la prueba? Las velocidades de prueba pueden variar de milisegundos a varios minutos.
  • Fidelidad: Cómo se ve en el "mundo real" es la prueba? Por ejemplo, si parte del código que estás probando, necesita hacer una solicitud de red, ¿el código de prueba esta solicitud de red o falsifica el resultado? Si la prueba realmente habla con la red, esto significa que tiene mayor fidelidad. La desventaja es que el la prueba podría tardar más en ejecutarse, podría generar errores si la red no funciona o podría ser costoso.

Consulta qué probar para obtener información sobre cómo comenzar a definir tu estrategia de prueba.

Aislamiento y dependencias

Cuando pruebas un elemento o sistema de elementos, lo haces en aislamiento. Para Por ejemplo, para probar un ViewModel, no necesitas iniciar un emulador e iniciar una IU. porque no depende (o no debería) del framework de Android.

Sin embargo, es posible que el sujeto de prueba dependa de otros para que funcione. Para por ejemplo, un ViewModel podría depender de un repositorio de datos para funcionar.

Cuando necesitas proporcionarle una dependencia a un sujeto de prueba, una práctica común es crear un doble de prueba (o, también, un objeto de prueba). Los dobles de prueba son objetos que verse y actuar como componentes en tu aplicación, pero se crean en tu prueba para proporcionan un comportamiento o datos específicos. La ventaja principal es que realizar pruebas de forma más rápida y sencilla.

Tipos de dobles de prueba

Existen varios tipos de dobles de prueba:

Falso Un doble de prueba que tiene la palabra "funciona" implementación de la clase, pero que se implementa de manera tal que sea buena para las pruebas, pero no adecuada para producción.

Ejemplo: Una base de datos en la memoria

Las falsificaciones no requieren un framework de simulación y son livianas. Son preferidas.

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

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

Stub Es un doble de prueba que comporta la forma en la 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 una 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 falsificaciones o las simulaciones en lugar de los espías.
Sombra Uso falso en Robolectric.

Ejemplo en el que se usa

Supongamos que quieres realizar una prueba de unidades de un ViewModel que depende de una interfaz. llamado UserRepository y expone el nombre del primer usuario en una IU. Puedes crea un doble de prueba falso implementando la interfaz y mostrando de datos no estructurados.

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 los datos locales y remotos de las fuentes que usaría la versión de producción. El archivo se encuentra en la fuente de prueba. y no se enviará con la app de producción.

Una dependencia falsa puede devolver datos conocidos sin depender de fuentes de datos remotas
Figura 1: Dependencia falsa en una prueba de unidades.

La siguiente prueba verifica que ViewModel exponga correctamente al primer usuario nombre 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, porque el El verificador crea ViewModel. Sin embargo, puede ser difícil 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 componentes de pruebas dobles es más complicado y requiere de tu app para que siga un diseño que se pueda probar.

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

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

Puedes diseñar tu app para lograr esta flexibilidad de forma manual, pero te recomendamos con un framework de inserción de dependencias, como Hilt, para reemplazar componentes. en tu app en el momento de la prueba. Consulta la guía de prueba de Hilt.

Robolectric

En Android, puedes usar el framework de Robolectric, que proporciona un tipo especial de doble de prueba. Robolectric te permite ejecutar las pruebas en en tu estación de trabajo o en un entorno de integración continua. Utiliza un JVM normal, sin un emulador ni dispositivo. Simula el aumento de las vistas, la carga de recursos y otras partes del framework de Android con dobles de prueba llamadas sombras.

Robolectric es un simulador, por lo que no debe reemplazar las pruebas de unidades simples ni usarse para realizar pruebas de compatibilidad. Proporciona velocidad y reduce los costos a expensas de menor fidelidad en algunos casos. Un buen enfoque para las pruebas de IU es hacerlas compatibles con las pruebas de Robolectric y las instrumentadas, y decide cuándo se en función de la necesidad de probar su funcionalidad o compatibilidad. Ambos Espresso y Compose se pueden ejecutar en Robolectric.