Principes de base des tests des applications Android

Cette page décrit les principes fondamentaux des tests des applications Android, y compris les bonnes pratiques centrales et leurs avantages.

Avantages des tests

Les tests font partie intégrante du processus de développement des applications. En effectuant régulièrement des tests sur votre application, vous pouvez vérifier son exactitude, son comportement fonctionnel et sa facilité d'utilisation avant de la mettre à disposition de tous.

Vous pouvez tester votre application manuellement en la parcourant. Vous pouvez utiliser différents appareils et émulateurs, modifier la langue du système et essayer de générer toutes les erreurs utilisateur ou de parcourir chaque parcours utilisateur.

Toutefois, les tests manuels ne s'adaptent pas correctement, et il peut être facile d'ignorer les régressions dans le comportement de votre application. Les tests automatisés impliquent l'utilisation d'outils qui effectuent des tests à votre place, ce qui est plus rapide, plus reproductible et vous fournit généralement des commentaires plus concrets sur votre application plus tôt dans le processus de développement.

Types de tests dans Android

Les applications mobiles sont complexes et doivent fonctionner dans de nombreux environnements. Ainsi, il existe de nombreux types de tests.

Objet

Par exemple, il existe différents types de tests en fonction du sujet:

  • Tests fonctionnels: mon application fait-elle ce qu'elle est censé faire ?
  • Tests de performances: sont-ils réalisés de manière rapide et efficace ?
  • Tests d'accessibilité: fonctionnent-ils bien avec les services d'accessibilité ?
  • Test de compatibilité: fonctionne-t-il correctement sur tous les appareils et niveaux d'API ?

Champ d'application

Les tests varient également en fonction de la taille ou du degré d'isolation:

  • Les tests unitaires ou les tests mineurs ne vérifient qu'une très petite partie de l'application, comme une méthode ou une classe.
  • Les tests de bout en bout ou les tests à grande échelle vérifient en même temps de plus grandes parties de l'application, comme un écran entier ou un parcours utilisateur.
  • Les tests moyens se situent entre les deux et vérifient l'intégration entre deux unités ou plus.
Les tests peuvent être petits, moyens ou grands.
Figure 1: Champs d'application des tests dans une application standard

Il existe de nombreuses façons de classer les tests. Cependant, la distinction la plus importante pour les développeurs d'applications réside dans l'emplacement des tests.

Tests d'instrumentation et tests en local

Vous pouvez effectuer des tests sur un appareil Android ou sur un autre ordinateur:

  • Les tests d'instrumentation s'exécutent sur un appareil Android physique ou émulé. L'application est créée et installée parallèlement à une application de test qui injecte des commandes et lit l'état. Les tests d'instrumentation sont généralement des tests d'interface utilisateur, qui permettent de lancer une application, puis d'interagir avec elle.
  • Les tests locaux s'exécutent sur votre ordinateur de développement ou sur un serveur. Ils sont donc également appelés tests côté hôte. Elles sont généralement petites et rapides, ce qui isole le sujet testé du reste de l'application.
Les tests peuvent être exécutés en tant que tests d'instrumentation sur un appareil ou en tant que tests en local sur votre ordinateur de développement.
Figure 2: Différents types de tests en fonction de leur emplacement.

Les tests unitaires ne sont pas tous locaux, et les tests de bout en bout ne s'exécutent pas tous sur un appareil. Par exemple :

  • Test local à grande échelle: vous pouvez utiliser un simulateur Android qui s'exécute localement, tel que Robolectric.
  • Test d'instrumentation à petite échelle: vous pouvez vérifier que votre code fonctionne bien avec une fonctionnalité de framework, telle qu'une base de données SQLite. Vous pouvez exécuter ce test sur plusieurs appareils pour vérifier l'intégration avec plusieurs versions de SQLite.

Exemples

Les extraits de code suivants montrent comment interagir avec l'interface utilisateur dans un test d'interface utilisateur d'instrumentation qui clique sur un élément et vérifie qu'un autre élément s'affiche.

Espresso

// When the Continue button is clicked
onView(withText("Continue"))
    .perform(click())

// Then the Welcome screen is displayed
onView(withText("Welcome"))
    .check(matches(isDisplayed()))

Compose UI

// When the Continue button is clicked
composeTestRule.onNodeWithText("Continue").performClick()

// Then the Welcome screen is displayed
composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()

Cet extrait montre une partie d'un test unitaire pour un ViewModel (test local côté hôte):

// Given an instance of MyViewModel
val viewModel = MyViewModel(myFakeDataRepository)

// When data is loaded
viewModel.loadData()

// Then it should be exposing data
assertTrue(viewModel.data != null)

Définir une stratégie de test

Dans l'idéal, vous testeriez chaque ligne de code de votre application sur tous les appareils avec lesquels elle est compatible. Malheureusement, cette approche est trop lente et trop coûteuse pour être pratique.

Une stratégie de test efficace permet de trouver un équilibre approprié entre la fidélité d'un test, sa vitesse et sa fiabilité. La similitude de l'environnement de test avec un appareil réel détermine la fidélité du test. Les tests de fidélité plus élevées s'exécutent sur des appareils émulés ou sur l'appareil physique lui-même. Les tests de basse fidélité peuvent s'exécuter sur la JVM de votre poste de travail local. Les tests haute fidélité sont souvent plus lents et nécessitent plus de ressources. Par conséquent, tous les tests ne doivent pas être un test haute fidélité.

Tests irréguliers

Des erreurs se produisent même dans les exécutions de test correctement conçues et implémentées. Par exemple, lors de l'exécution d'un test sur un appareil réel, une mise à jour automatique peut démarrer au milieu d'un test et entraîner son échec. Des conditions de concurrence subtiles dans votre code peuvent ne se produire qu'un faible pourcentage du temps. Les tests qui échouent à 100% du temps sont irréguliers.

Architecture testable

Avec une architecture d'application testable, le code suit une structure qui vous permet de tester facilement différentes parties de manière isolée. Les architectures testables présentent d'autres avantages, tels qu'une meilleure lisibilité, une gestion, une évolutivité et une réutilisabilité.

Une architecture qui ne peut pas être testée produit ce qui suit:

  • Tests plus importants, plus lents et plus irréguliers. Les classes qui ne peuvent pas faire l'objet de tests unitaires devront peut-être être couvertes par des tests d'intégration ou des tests d'interface utilisateur plus importants.
  • Il y a moins d'opportunités de tester différents scénarios. Les tests plus importants étant plus lents, tester tous les états possibles d'une application peut être irréaliste.

Pour en savoir plus sur les consignes relatives à l'architecture, consultez le guide de l'architecture des applications.

Approches de découplage

Si vous pouvez extraire une partie d'une fonction, classe ou module à partir du reste, le test est plus facile et plus efficace. Cette pratique est connue sous le nom de découplage. C'est le concept le plus important pour une architecture testable.

Les techniques de découplage courantes sont les suivantes:

  • Divisez une application en calques tels que "Présentation", "Domaine" et "Données". Vous pouvez également diviser une application en modules, un par fonctionnalité.
  • Évitez d'ajouter une logique à des entités ayant des dépendances importantes, telles que des activités et des fragments. Utilisez ces classes comme points d'entrée du framework et déplacez l'UI et la logique métier, par exemple vers un composable, un ViewModel ou une couche de domaine.
  • Évitez les dépendances du framework directes dans les classes contenant la logique métier. Par exemple, n'utilisez pas de contextes Android dans les ViewModels.
  • Facilitez le remplacement des dépendances. Par exemple, utilisez des interfaces plutôt que des implémentations concrètes. Utilisez l'injection de dépendances même si vous n'utilisez pas de framework DI.

Étapes suivantes

Maintenant que vous savez pourquoi effectuer des tests et quels sont les deux principaux types de tests, vous pouvez lire la section Éléments à tester.

Si vous souhaitez créer votre premier test et apprendre en le faisant, consultez les ateliers de programmation sur les tests.