Lors de la conception de la stratégie de test d'un élément ou d'un système, trois aspects sont associés:
- Portée: quelle partie du code le test porte-t-il ? Les tests peuvent vérifier une seule méthode, l'ensemble de l'application ou quelque part entre les deux. Le champ d'application testé est en cours de test et fait généralement référence à l'objet du test, mais également au système en cours de test ou à l'unité en cours de test.
- Vitesse: à quelle vitesse le test se déroule-t-il ? Les vitesses de test peuvent varier de l'ordre de quelques millisecondes à plusieurs minutes.
- Fidélité: dans quelle mesure le test est-il "dans le monde réel" ? Par exemple, si une partie du code que vous testez doit envoyer une requête réseau, le code de test effectue-t-il réellement cette requête réseau ou simule-t-il le résultat ? Si le test communique réellement avec le réseau, cela signifie qu'il a une plus grande fidélité. En contrepartie, l'exécution du test peut prendre plus de temps, entraîner des erreurs si le réseau est en panne ou être co��teux.
Découvrez les éléments à tester pour découvrir comment définir votre stratégie de test.
Isolation et dépendances
Lorsque vous testez un élément ou un système d'éléments, vous le faites de manière isolée. Par exemple, pour tester un ViewModel, vous n'avez pas besoin de démarrer un émulateur et de lancer une UI, car il ne dépend pas (ou ne devrait pas) du framework Android.
Cependant, le sujet testé peut dépender d'autres sujets pour que le test fonctionne. Par exemple, un ViewModel peut dépendre d'un dépôt de données pour fonctionner.
Lorsque vous devez fournir une dépendance à un sujet soumis au test, une pratique courante consiste à créer un double de test (ou un objet de test). Les doubles de test sont des objets qui ressemblent à des composants de votre application et agissent en tant que composants dans votre application, mais qui sont créés dans votre test pour fournir un comportement ou des données spécifiques. Leur principal avantage est qu'ils simplifient et accélèrent les tests.
Types de doubles de test
Il existe différents types de doubles de test:
Falsifiée | Double de test avec une implémentation "fonctionnelle" de la classe, mais implémentée de manière à être efficace pour les tests, mais inadaptée à la production.
Exemple: une base de données en mémoire. Les faux ne nécessitent pas de framework de simulation et sont légers. Elles sont à privilégier. |
---|---|
Simulation | Double de test qui se comporte de la manière dont vous le programmez et qui a des attentes concernant ses interactions. Les simulations échoueront aux tests si leurs interactions ne répondent pas aux exigences que vous définissez. Pour ce faire, les simulations sont généralement créées à l'aide d'un framework de simulation.
Exemple: Vérifier qu'une méthode dans une base de données a été appelée exactement une fois |
Stub | Double de test qui se comporte comme vous l'avez programmé, mais qui n'a pas d'attentes concernant ses interactions. Généralement créé avec un framework de simulation. Pour plus de simplicité, les faux sont préférables aux bouchons. |
Factice | Double de test transmis, mais non utilisé, par exemple si vous devez simplement le fournir en tant que paramètre.
Exemple: une fonction vide transmise en tant que rappel de clic. |
Espion | Wrapper sur un objet réel qui garde également une trace de certaines informations supplémentaires, comme pour les simulations. Elles sont généralement évités pour ajouter de la complexité. Les faux ou les sketches sont donc privilégiés par rapport aux espions. |
Shadow | Faux utilisé dans Robolectric. |
Exemple utilisant un faux
Supposons que vous souhaitiez effectuer un test unitaire d'un ViewModel qui dépend d'une interface appelée UserRepository
et qui expose le nom du premier utilisateur à une UI. Vous pouvez créer un double de test en implémentant l'interface et en renvoyant les données connues.
object FakeUserRepository : UserRepository {
fun getUsers() = listOf(UserAlice, UserBob)
}
val const UserAlice = User("Alice")
val const UserBob = User("Bob")
Ce faux UserRepository
n'a pas besoin de dépendre des sources de données locales et distantes que la version de production utiliserait. Le fichier se trouve dans l'ensemble de sources de test et ne sera pas envoyé avec l'application de production.
Le test suivant vérifie que le ViewModel expose correctement le premier nom d'utilisateur à la vue.
@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)
}
Vous pouvez facilement remplacer UserRepository
par un faux lors d'un test unitaire, car le ViewModel est créé par le testeur. Toutefois, il peut être difficile de remplacer des éléments arbitraires dans des tests plus importants.
Remplacer des composants et injection de dépendances
Lorsque les tests n'ont aucun contrôle sur la création des systèmes soumis aux tests, le remplacement des composants par les doubles de test devient plus complexe et nécessite que l'architecture de votre application suive une conception testable.
Même les tests de bout en bout de grande ampleur peuvent bénéficier de l'utilisation de doubles de test, tels qu'un test d'interface utilisateur instrumenté qui parcourt un flux utilisateur complet dans votre application. Dans ce cas, vous pouvez rendre votre test hermétique. Un test hermétique évite toutes les dépendances externes, telles que l'extraction de données sur Internet. Cela améliore la fiabilité et les performances.
Vous pouvez concevoir votre application manuellement pour obtenir cette flexibilité, mais nous vous recommandons d'utiliser un framework d'injection de dépendances tel que Hilt pour remplacer les composants de votre application au moment du test. Consultez le guide des tests Hilt.
Robolectric
Sous Android, vous pouvez utiliser le framework Robolectric, qui fournit un type spécial de double de test. Robolectric vous permet d'exécuter vos tests sur votre poste de travail ou dans votre environnement d'intégration continue. Il utilise une JVM standard, sans émulateur ni appareil. Il simule le gonflage des vues, du chargement des ressources et d'autres parties du framework Android avec des doubles de test appelés ombres.
Robolectric est un simulateur. Il ne doit donc pas remplacer les tests unitaires simples ni être utilisé pour effectuer des tests de compatibilité. Elle offre de la vitesse et réduit les coûts au détriment d'une fidélité inférieure dans certains cas. Une bonne approche pour les tests d'interface utilisateur consiste à les rendre compatibles à la fois avec les tests Robolectric et les tests d'instrumentation, et à décider quand les exécuter en fonction de la nécessité de tester la fonctionnalité ou la compatibilité. Les tests Espresso et Compose peuvent s'exécuter sur Robolectric.