Skip to content

Blog


Un cadre pour le développement rapide et évolutif de tests d'interface utilisateur Android

19 août 2020

|

Nishant Soni

 Le développement rapide de fonctionnalités et la productivité des ingénieurs mobiles ont longtemps été freinés par les tests d'interface utilisateur, un processus lent mais essentiel. Si les nouvelles technologies de tests automatisés de l'interface utilisateur, comme UI Automator ou Espresso, ont aidé les développeurs à écrire des tests de l'interface utilisateur, ces outils ne permettent pas de conserver un code propre, organisé et facile à lire. Cela nuit finalement à la productivité et à l'évolutivité et continue de faire des tests d'interface utilisateur un goulot d'étranglement pour le développement. 

Heureusement, les entreprises qui se débattent avec les tests d'interface utilisateur peuvent améliorer les outils d'automatisation des tests d'interface utilisateur en utilisant un modèle de conception Fluent pour créer des tests faciles à lire et à gérer, qui sont rapides à mettre en œuvre et qui permettront l'évolutivité. 

Chez DoorDash, tester tous les scénarios d'interface utilisateur d'une nouvelle version prenait deux jours entiers à trois développeurs et un responsable de l'assurance qualité, ce qui ralentissait notre cycle de développement à une version toutes les deux semaines. Ce processus, bien qu'essentiel pour détecter les bogues nuisibles aux utilisateurs, nuisait au moral de l'équipe et à la productivité en général. Pour résoudre ce problème, nous avons construit un framework basé sur le modèle de conception Fluent qui nous a permis d'utiliser des outils d'automatisation de l'interface utilisateur en rendant les tests faciles à lire et évolutifs. 

Pour démontrer comment nous avons augmenté la vélocité de nos tests, nous allons d'abord passer en revue les problèmes liés à l'utilisation d'UI Automator et d'autres approches de test. Ensuite, nous présenterons les patrons de conception, les patrons de conception Fluent, et la façon dont nous les avons implémentés chez DoorDash. 

Les défis des tests d'interface utilisateur

Les tests rapides et évolutifs de l'interface utilisateur constituent un défi majeur pour garantir l'absence de bogues dans le développement d'applications mobiles, car les outils d'automatisation des tests ne produisent pas de tests faciles à lire et évolutifs, et les autres solutions sont tout aussi fastidieuses. 

Alors que des outils comme UI Automator ou Espresso utilisant Android Studio ont permis aux ingénieurs de commencer plus facilement à écrire des tests sur Android pour simuler le comportement de l'utilisateur, les tests sont difficiles à comprendre et à gérer à l'échelle. Si les tests peuvent convenir au début, l'augmentation du nombre de tests rend plus difficile la compréhension du code de test, ce qui pose un problème de maintenance à long terme.  

Les outils d'automatisation des tests peuvent produire un code de test dans lequel chaque action est décrite en trois ou quatre lignes d'instructions, au lieu de lignes concises avec des descripteurs clairs, en utilisant le langage des affaires, comme le montre la figure 1 ci-dessous :

Les tests d'automatisation créés sans modèle de conception peuvent donner lieu à un code obscur qui n'énonce pas l'intention du test.
Figure 1 : Les tests d'automatisation créés sans modèle de conception peuvent donner lieu à un code obscur qui n'énonce pas l'intention du test.

L'alternative à l'utilisation de plates-formes de test automatisées consiste à faire appel à des testeurs manuels. Malheureusement, cette solution ne permet pas vraiment de gagner du temps, car les testeurs manuels ont besoin de transferts de connaissances et la gestion de la délégation prend presque autant de temps et d'efforts que si les développeurs effectuaient eux-mêmes les tests. En outre, les tests manuels ne sont pas aussi précis que les tests automatisés, car ils laissent place à davantage d'erreurs humaines, et ils ne sont pas rentables pour les régressions à haut volume.

Comment un modèle de conception peut aider à l'automatisation de l'interface utilisateur

Les problèmes liés aux tests manuels de l'interface utilisateur peuvent être résolus en choisissant un bon modèle de conception et le bon cadre pour les tests de l'interface utilisateur. Un bon cadre de test d'automatisation doit permettre aux ingénieurs d'écrire des tests qui sont.. :

  • Facile à comprendre et à lire
  • Rapidement rédigé
  • Maintenable
  • Évolutif

Il existe plusieurs modèles de conception qui sont couramment utilisés pour les tests d'automatisation Web, le plus populaire étant le modèle Page Object décrit par Martin Fowler en 2013. En appliquant ce modèle de conception à l'exemple de la figure 1 ci-dessus, nous pouvons constater une nette amélioration de la lisibilité du code de test, comme le montre la figure 2 ci-dessous :

Un test d'automatisation utilisant le modèle Page Object produit un test mieux organisé et plus concis que dans notre exemple précédent.
Figure 2 : Un test d'automatisation utilisant le modèle Page Object produit un test mieux organisé et plus concis que dans notre exemple précédent.

Le code de la figure 2 est bien meilleur que celui avec lequel nous avons commencé :

  • Chaque action peut être effectuée en une seule ligne 
  • Les détails sont extraits à l'intérieur d'une fonction
  • Cette fonction peut être réutilisée chaque fois que cette action est à nouveau requise 

Cependant, l'adoption d'un tel modèle de conception pose encore quelques problèmes :

  • Le test ne montre toujours pas clairement son intention ; il ressemble plutôt à des instructions codées.
  • Il y aura toujours beaucoup de duplication de code, ce qui n'est pas idéal

Utilisation d'une interface fluide pour mettre en évidence la logique commerciale

Un modèle de conception Fluent nous offre le meilleur des deux mondes, car il démontre une intention claire en utilisant un langage spécifique au domaine. Une interface fluide est une API orientée objet dont la conception repose largement sur l'enchaînement de méthodes. L'objectif est d'améliorer la lisibilité du code en utilisant un langage spécifique au domaine, permettant de relayer le contexte d'instruction d'un appel ultérieur.

Comment un modèle de conception Fluent démontre une intention claire

Les modèles de conception doivent avoir une intention claire et utiliser un langage spécifique au domaine qui peut presque être lu comme un langage conversationnel. Une interface fluide répond à ces critères car elle nous permet d'utiliser des noms d'API fluides et un langage spécifique au domaine.  

Les avantages de l'utilisation d'un modèle de conception Fluent sont les suivants :

  • Lorsque le code de test est facile à comprendre, il est facile à étendre et à réutiliser.
  • La facilité d'utilisation aidera les développeurs à travailler plus rapidement et à être plus confiants lors de la rédaction des tests. 
  • Le modèle de conception est indépendant des outils sous-jacents tels que UI Automator ou Espresso.

Utilisation d'un modèle de conception Fluent pour construire l'automatisation des tests de l'interface utilisateur de l'application Dasher

Chez Doordash, nous utilisions TestRail pour tester manuellement l'application avant chaque lancement. Il fallait trois ingénieurs logiciels et un ingénieur en assurance qualité pour passer en revue les tests TestRail et une demi-journée chacun pour les exécuter, ce qui prenait deux journées de travail complètes. Ce processus limitait le nombre de versions de notre application à toutes les deux semaines. 

La mise en place d'un nouveau cadre d'automatisation de l'interface utilisateur pour Android a permis d'éliminer ces points douloureux pour les cycles de publication. Nous allons maintenant entrer un peu plus dans les détails pour expliquer l'approche et les outils que nous avons utilisés, l'architecture globale de haut niveau de la solution, et partager quelques bonnes pratiques.

Notre approche de l'utilisation des modèles de conception Fluent 

En général, chaque scénario de test de l'interface utilisateur implique des interactions avec des activités et des écrans ; sur chaque écran, l'utilisateur effectue une action et attend un comportement en conséquence. Nous utilisons ensuite des assertions pour vérifier les résultats. 

Pour réaliser ces tests, nous structurons le code de test de manière à ce que chaque écran encapsule les actions qui sont effectuées sur cet écran et puisse vérifier le comportement après avoir effectué ces actions. Toutes les interactions sont nommées dans un langage spécifique au domaine en utilisant l'interface du modèle de conception Fluent, comme le montre la figure 3 ci-dessous :

Dans notre cadre d'automatisation de l'interface utilisateur pour notre application Android Dasher, nous utilisons un modèle de conception Fluent pour nommer les interactions dans notre langage spécifique au domaine.
Figure 3 : Dans notre cadre d'automatisation de l'interface utilisateur pour notre application Android Dasher, nous utilisons un modèle de conception Fluent pour nommer les interactions dans notre langage spécifique au domaine.

Suite de tests 

Le choix des outils joue un rôle important dans l'amélioration de la productivité des développeurs. Nous estimons que nos outils sont faciles à utiliser et qu'ils bénéficient d'une bonne assistance en ligne. 

Outils de test de l'interface utilisateur : Avant de développer nos procédures de test de l'interface utilisateur, nous avons envisagé différents outils pour écrire des tests de l'interface utilisateur, tels qu'Appium, un outil tiers. Cependant, nous avons constaté que les outils natifs d'Android étaient plus faciles à utiliser et bénéficiaient d'une meilleure prise en charge. Il existe deux outils de test de l'interface utilisateur pris en charge par Google, qui peuvent être exécutés séparément ou ensemble, puisqu'ils fonctionnent sous le même programme d'exécution des tests d'instrumentation :

  • UI Autom ator - UI Automator est un cadre de test de l'interface utilisateur adapté aux tests fonctionnels de l'interface utilisateur entre les applications installées et le système.
  • Espresso - L'un des principaux avantages de l'utilisation d'Espresso est qu'il permet de synchroniser automatiquement les actions de test avec l'interface utilisateur de l'application testée. Espresso détecte lorsque le thread principal est inactif, ce qui lui permet d'exécuter les commandes de test au moment opportun, améliorant ainsi la fiabilité des tests. 

IDE :

  • Android Studio: Pour les développeurs qui pensent que les tests d'interface utilisateur font partie intégrante du développement d'une application, Android Studio leur facilitera la vie. Il permet d'exécuter les tests unitaires, les tests de l'interface utilisateur Android et l'application elle-même dans le même environnement de développement. Il permet également de structurer les paquets de manière à ce que le code de l'application et les tests correspondants (tests unitaires et tests d'interface utilisateur) puissent résider dans le même référentiel, ce qui facilite la maintenance des versions du code de l'application et des tests correspondants.

Dispositifs d'essai ciblés :

  • Les tests d'interface utilisateur sont généralement exécutés sur un appareil réel ou sur un émulateur afin d'imiter le scénario de test. Pour la plupart de nos cas de test, nous utilisons des émulateurs pour les configurations et tailles d'appareils les plus courantes.

CI/CD :

  • Bitrise est l'un des outils CI/CD les plus populaires sur le cloud qui permet une mise à l'échelle et une facilité d'utilisation pour la mise en place d'environnements de test. En particulier pour les tests d'interface utilisateur, il permet l'intégration à la fois d'un parc d'appareils et d'appareils virtuels et est devenu un outil facile pour mettre en place un environnement de construction et de test pour les développeurs.

Processus de test

Nous écrivons des scénarios de test, illustrés dans la figure 3 ci-dessus, dans un langage spécifique au domaine, en suivant une approche axée sur le comportement. Ces tests utilisent l'API de configuration des tests pour créer l'environnement d'un test particulier et utilisent des objets d'écran qui interagissent avec les écrans et vérifient les actions. L'interaction et la vérification des écrans sont finalement effectuées par un outil d'automatisation des tests, tel que UI Automator, Espresso ou tout autre outil similaire. 

Pour comprendre ce processus, examinons un exemple de flux de connexion d'une application utilisant un modèle de conception Fluent et notre architecture de test décrite ci-dessus.

Test de connexion :

L'utilisation du modèle de conception Fluent dans nos tests d'automatisation permet d'obtenir un code simplifié et facile à lire.
Figure 4 : L'utilisation du modèle de conception Fluent dans nos tests d'automatisation permet d'obtenir un code simplifié et facile à lire.

Le test de la figure 4 est écrit en utilisant le modèle de conception Fluent, et la classe de base qui permet ce modèle s'appelle Screen.kt. Le code de Screen.kt est présenté dans la figure 5 ci-dessous :

image10
Figure 5 : La classe de base Screen.kt permet d'appliquer le modèle de conception Fluent.

All the screen classes extend this class and follow the pattern of returning itself for each interaction/verification function, thereby passing the context along. The inline generic method “<reified T: Screen> on()” is used to switch context from one screen to another. An example of the “Screen” implementation is shown in Figure 6, below:

Figure 6 : Bien que la classe LoginScreen soit mise en œuvre avec UI Automator dans cet exemple, elle pourrait facilement être remplacée par un autre outil pour la même action, mais avec une approche différente.
Figure 6 : Bien que la classe LoginScreen soit mise en œuvre avec UI Automator dans cet exemple, elle pourrait facilement être remplacée par un autre outil pour la même action, mais avec une approche différente.

L'implémentation ci-dessus utilise l'outil sous-jacent, UI Automator, pour interagir avec l'écran. Bien que cet exemple utilise UI Automator, il peut être remplacé par Espresso ou tout autre outil similaire sans affecter la logique d'entreprise ou les attentes en matière de test.

Révision de la structure des dossiers

Pour les codeurs propres et les concepteurs de logiciels, la principale propriété d'un paquet est la possibilité d'avoir un nom significatif qui décrit son objectif et sa raison d'être. C'est pourquoi nous avons organisé nos paquets comme suit :

  1. test : Contient tous les tests pour différents scénarios
  2. écran : Contient tous les écrans de l'application et les interactions correspondantes.
  3. UI Automator/Espresso : contient des classes d'outils permettant d'effectuer des interactions à l'écran et de vérifier les comportements.
  4. utils : API commune permettant de configurer l'environnement pour l'exécution d'un test, par exemple la création et l'attribution d'ordres avant le démarrage de Dashing. Elle contient également d'autres fonctions utilitaires communes.
Nous organisons nos paquets de manière à ce qu'ils puissent être nommés de manière pertinente.
Figure 7 : Nous organisons nos paquets de manière à ce qu'ils puissent être nommés de manière pertinente.

Bonnes pratiques pour l'utilisation de cette approche 

En écrivant ces tests, nous avons développé quelques bonnes pratiques qui nous ont aidés à garder notre code propre, lisible et extensible. En voici quelques-unes :

  • Convention de nommage : Même pour la classe UIAutomator, nous continuons à utiliser le modèle de conception Fluent, qui se lit comme un langage spécifique au domaine.
    • UiAutomator.kt : cette classe comportera essentiellement deux types de fonctions : toute action de l'utilisateur sur l'écran et la vérification du comportement.
      • Verification function name uses this pattern: hasViewBy<Class,Text,ContenDesc,ResourceId etc that identifies the view>
      • Action function name has this pattern: <click,swipe,scroll,set><Button/View>By<Button/View identifies>
    • L'écran : Il est très important d'utiliser ici le modèle de conception Fluent et de trouver un nom correct pour les fonctions afin qu'elles soient plus fluides lors de la lecture du test.
      • Le nom de la classe d'écran correspond à la fonction de l'écran, par exemple PickUpItemScreen().
      • Le nom de la fonction de vérification est rédigé dans un langage spécifique au domaine, par exemple verifyAmount(), verifySignatureStepComplete(), verifyCompleteStepsGetCxSignature(), etc.
      • Le nom de la fonction d'action est également dans un langage spécifique au domaine, par exemple clickStartCateringSetup(), slideBeforeCateringSetupComplete() etc.
  • Nous devrions toujours ajouter un journal dans chaque fonction de la classe d'écran, ce qui permet de dépanner plus rapidement les journaux CI/CD.
  • Toute boîte de dialogue/feuille de bas de page pertinente pour un écran est définie comme une classe imbriquée de l'écran parent.
  • Toutes les vérifications auraient dû faire l'objet d'une assertion accompagnée d'un message de journal indiquant clairement la raison de l'échec de l'assertion, comme le montre la figure 8 ci-dessous :
Figure 8 : L'inclusion d'un message d'échec dans les assertions facilite le dépannage des tests qui ont échoué.
Figure 8 : L'inclusion d'un message d'échec dans les assertions facilite le dépannage des tests qui ont échoué.

Les modèles de conception fluides augmentent la vitesse des développeurs

Une fois le cadre initial mis en place, nous avons terminé 70 % de nos tests de régression en deux mois. Voici quelques-uns des résultats :

  • Notre couverture de code est passée de 0 % à ~40 %.
  • Nos tests manuels ont été quatre fois plus rapides, passant de 16 à 4 heures.
  • Les cycles de publication sont passés d'une version toutes les deux semaines à des versions hebdomadaires, et nous pouvons effectuer des tests de régression à tout moment. 
  • L'équipe est plus productive car il suffit d'écrire les tests pour les nouvelles fonctionnalités ou de mettre à jour les fonctionnalités existantes, ce qui est beaucoup plus rapide à développer.

Conclusion

L'utilisation de l'interface fluide pour les tests d'interface utilisateur a libéré les ingénieurs des tâches répétitives et chronophages, ce qui leur a permis de consacrer plus de temps à la résolution de cas délicats. L'amélioration de la couverture du code et l'exécution des tests automatisés pour les tests de régression ont assuré la robustesse de notre application Android Dasher. 

Comme la structure du code est indépendante de l'outil de test sous-jacent (UI Automator ou Espresso), nous pouvons facilement adopter tout outil plus performant publié à l'avenir.

A propos de l'auteur

  • Nishant Soni

Emplois connexes

Localisation
San Francisco, CA; Oakland, CA
Département
Ingénierie
Localisation
Toronto, ON
Département
Ingénierie
Localisation
San Francisco, CA ; Sunnyvale, CA
Département
Ingénierie
Localisation
San Francisco, CA ; Mountain View, CA ; New York, NY ; Seattle, WA
Département
Ingénierie
Localisation
San Francisco, CA ; Sunnyvale, CA ; Seattle, WA
Département
Ingénierie