Stratégies de test

Les tests automatisés vous aident à améliorer la qualité de votre application de plusieurs façons. Par exemple, il vous aide à effectuer des validations, à détecter les régressions et à vérifier la compatibilité. Une bonne stratégie de test vous permet de tirer parti des tests automatisés pour vous concentrer sur un avantage important: la productivité des développeurs.

Les équipes atteignent des niveaux de productivité supérieurs lorsqu'elles utilisent une approche systématique des tests associée à des améliorations de l'infrastructure. Cela permet d'obtenir des retours en temps opportun sur le comportement du code. Une stratégie de test efficace offre les avantages suivants:

  • Détecte les problèmes dès que possible.
  • S'exécute rapidement.
  • Fournit des indications claires lorsqu'un problème doit être corrigé.

Cette page vous aide à décider quels types de tests implémenter, où les exécuter et à quelle fréquence.

Pyramide des tests

Dans les applications modernes, vous pouvez classer les tests par taille. Les petits tests ne portent que sur une petite partie du code, ce qui les rend rapides et fiables. Les grands tests ont un champ d'application étendu et nécessitent des configurations plus complexes qui sont difficiles à gérer. Toutefois, les grands tests sont plus fidèles* et peuvent détecter beaucoup plus de problèmes en une seule fois.

*Fidelity fait référence à la similarité entre l'environnement d'exécution de test et l'environnement de production.

La répartition du nombre de tests par champ d'application est généralement représentée sous forme de pyramide.
Figure 1. La répartition du nombre de tests par champ d'application est généralement représentée sous forme de pyramide.

La plupart des applications doivent comporter de nombreux petits tests et relativement peu de grands tests. La distribution des tests dans chaque catégorie doit former une pyramide, les tests de petite envergure les plus nombreux formant la base et les tests de grande envergure les moins nombreux formant l'extrémité.

Minimiser le coût d'un bug

Une bonne stratégie de test maximise la productivité des développeurs tout en minimisant le coût de la détection des bugs.

Prenons un exemple de stratégie potentiellement inefficace. Ici, le nombre de tests par taille ne s'organise pas en pyramide. Il y a trop de tests de bout en bout volumineux et trop peu de tests d'interface utilisateur des composants:

Stratégie déséquilibrée dans laquelle de nombreux tests sont effectués manuellement et que les tests sur l'appareil ne sont exécutés que tous les soirs.
Figure 2. Stratégie déséquilibrée dans laquelle de nombreux tests sont effectués manuellement et que les tests sur l'appareil ne sont exécutés que tous les soirs.

Cela signifie que trop peu de tests sont exécutés avant la fusion. En cas de bug, les tests risquent de ne pas le détecter avant l'exécution des tests de bout en bout quotidiens ou hebdomadaires.

Il est important de réfléchir aux conséquences de ce fait sur le coût d'identification et de correction des bugs, et de comprendre pourquoi il est important de privilégier des tests plus petits et plus fréquents:

  • Lorsqu'un bug est détecté par un test unitaire, il est généralement corrigé en quelques minutes, ce qui réduit les coûts.
  • Un test de bout en bout peut prendre des jours pour détecter le même bug. Cela peut impliquer plusieurs membres de l'équipe, ce qui réduit la productivité globale et peut retarder une publication. Le coût de ce bug est plus élevé.

Toutefois, une stratégie de test inefficace est préférable à l'absence de stratégie. Lorsqu'un bug passe en production, la correction prend beaucoup de temps à être implémentée sur les appareils des utilisateurs, parfois plusieurs semaines. La boucle de rétroaction est donc la plus longue et la plus coûteuse.

Une stratégie de test évolutive

La pyramide des tests est traditionnellement divisée en trois catégories:

  • Tests unitaires
  • Tests d'intégration
  • Tests de bout en bout.

Toutefois, ces concepts n'ont pas de définitions précises. Les équipes peuvent donc définir leurs catégories différemment, par exemple en utilisant cinq couches:

Pyramide de tests à cinq niveaux avec les catégories tests unitaires, tests de composants, tests de fonctionnalités, tests d'application et tests de version candidate, par ordre croissant.
Figure 3. Pyramide de test à cinq couches
  • Un test unitaire est exécuté sur la machine hôte et vérifie une seule unité fonctionnelle de logique sans dépendances sur le framework Android.
    • Exemple: vérifier les erreurs de 1 à 1 dans une fonction mathématique.
  • Un test de composant vérifie la fonctionnalité ou l'apparence d'un module ou d'un composant indépendamment des autres composants du système. Contrairement aux tests unitaires, la surface d'un test de composant s'étend à des abstractions plus élevées au-dessus des méthodes et des classes individuelles.
  • Un test de fonctionnalité vérifie l'interaction d'au moins deux composants ou modules indépendants. Les tests de fonctionnalité sont plus importants et plus complexes, et fonctionnent généralement au niveau de la fonctionnalité.
  • Un test d'application vérifie le fonctionnement de l'ensemble de l'application sous la forme d'un binaire déployable. Il s'agit de grands tests d'intégration qui utilisent un binaire débogable, tel qu'un build de développement pouvant contenir des hooks de test, comme système testé.
    • Exemple: Test du comportement de l'UI pour vérifier les modifications de configuration dans un appareil pliable, tests de localisation et d'accessibilité
  • Un test version candidate vérifie le fonctionnement d'un build. Ils sont semblables aux tests d'application, sauf que le binaire de l'application est minimisé et optimisé. Il s'agit de grands tests d'intégration de bout en bout exécutés dans un environnement aussi proche de la production que possible, sans exposer l'application à des comptes utilisateur ou des backends publics.

Cette classification tient compte de la fidélité, du temps, de la portée et du niveau d'isolation. Vous pouvez avoir différents types de tests sur plusieurs couches. Par exemple, la couche de test de l'application peut contenir des tests de comportement, de capture d'écran et de performances.

Champ d'application

Accès au réseau

Exécution

Type de build

Cycle de vie

Unité

Méthode ou classe unique avec des dépendances minimales.

Non

Local

Débogable

Préfusion

Composant

Niveau du module ou du composant

Plusieurs cours ensemble

Non

Local
Robolectric
Émulateur

Débogable

Préfusion

Fonctionnalité

Niveau des fonctionnalités

Intégration à des composants appartenant à d'autres équipes

Simulation

Local
Robolectric
Émulateur
Appareils

Débogable

Préfusion

Application

Au niveau de l'application

Intégration à des fonctionnalités et/ou services appartenant à d'autres équipes

Simulé
Serveur de préproduction
Serveur de production

Émulateur
Appareils

Débogable

Pré-fusion
Post-fusion

Version candidate

Au niveau de l'application

Intégration à des fonctionnalités et/ou services appartenant à d'autres équipes

Serveur de production

Émulateur
Appareils

Build réduit

Post-fusion
Version préliminaire

Déterminer la catégorie de test

En règle générale, vous devez envisager la couche la plus basse de la pyramide qui peut fournir à l'équipe le niveau de commentaires approprié.

Par exemple, réfléchissez à la façon de tester l'implémentation de cette fonctionnalité: l'UI d'un parcours de connexion. En fonction de ce que vous devez tester, vous choisirez différentes catégories:

Objet testé

Description de ce qui est testé

Catégorie de test

Exemple de type de test

Logique de l'outil de validation des formulaires

Classe qui valide l'adresse e-mail par rapport à une expression régulière et vérifie que le champ de mot de passe a été saisi. Il n'a aucune dépendance.

Tests unitaires

Test unitaire JVM local

Comportement de l'interface utilisateur du formulaire de connexion

Un formulaire avec un bouton qui n'est activé que lorsque le formulaire a été validé

Tests de composants

Test de comportement de l'UI exécuté sur Robolectric

Apparence de l'UI du formulaire de connexion

Un formulaire conforme à une spécification UX

Tests de composants

Test de capture d'écran de l'aperçu de Compose

Intégration avec le gestionnaire d'authentification

UI qui envoie des identifiants à un gestionnaire d'authentification et reçoit des réponses pouvant contenir différentes erreurs.

Tests de fonctionnalités

Test de la JVM avec des faux

Boîte de dialogue de connexion

Écran affichant le formulaire de connexion lorsque l'utilisateur appuie sur le bouton de connexion.

Tests d'applications

Test de comportement de l'UI exécuté sur Robolectric

Parcours utilisateur critique: connexion

Flux de connexion complet utilisant un compte de test sur un serveur intermédiaire

Version finale

Test de comportement de l'UI Compose de bout en bout exécuté sur l'appareil

Dans certains cas, le fait qu'un élément appartienne à une catégorie ou à une autre peut être subjectif. D'autres raisons peuvent expliquer qu'un test soit déplacé vers le haut ou vers le bas, comme le coût de l'infrastructure, l'instabilité et les longs délais de test.

Notez que la catégorie de test ne dicte pas le type de test et que toutes les fonctionnalités ne doivent pas être testées dans chaque catégorie.

Les tests manuels peuvent également faire partie de votre stratégie de test. En général, les équipes de contrôle qualité effectuent des tests de version candidate, mais elles peuvent également être impliquées à d'autres étapes. Par exemple, des tests exploratoires pour détecter les bugs d'une fonctionnalité sans script.

Infrastructure de test

Une stratégie de test doit s'appuyer sur une infrastructure et des outils pour aider les développeurs à exécuter leurs tests en continu et à appliquer des règles qui garantissent la réussite de tous les tests.

Vous pouvez classer les tests par portée pour définir quand et où exécuter les tests. Par exemple, en suivant le modèle à cinq couches:

Catégorie

Environnement (où)

Déclencheur (quand)

Unité

[Local][4]

Chaque commit

Composant

Local

Chaque commit

Fonctionnalité

Émulateurs et locaux

Avant la fusion, avant de fusionner ou d'envoyer une modification

Application

Local, émulateurs, 1 téléphone, 1 pliable

Après la fusion, après avoir fusionné ou envoyé une modification

Version candidate

8 téléphones différents, 1 pliable et 1 tablette

Avant la sortie

  • Les tests unitaires et composants s'exécutent sur le système d'intégration continue pour chaque nouveau commit, mais uniquement pour les modules concernés.
  • Tous les tests Unit, Component et Feature s'exécutent avant la fusion ou l'envoi d'une modification.
  • Les tests d'application s'exécutent après la fusion.
  • Les tests Release Candidate s'exécutent la nuit sur un téléphone, un appareil pliable et une tablette.
  • Avant une version, les tests Release Candidate sont exécutés sur un grand nombre d'appareils.

Ces règles peuvent changer au fil du temps lorsque le nombre de tests affecte la productivité. Par exemple, si vous appliquez une cadence nocturne aux tests, vous risquez de réduire les durées de compilation et de test de l'intégration continue, mais vous pouvez également prolonger la boucle de rétroaction.