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é plus élevés lorsqu'elles utilisent une approche systématique pour les tests, associée à des améliorations de l'infrastructure. Cela permet d'obtenir rapidement des commentaires sur le comportement du code. Une bonne stratégie de test doit :

  • Détecte les problèmes le plus tôt possible.
  • S'exécute rapidement.
  • Fournit des indications claires lorsqu'un problème doit être résolu.

Cette page vous aidera à choisir les types de tests à implémenter, où les exécuter et à quelle fréquence.

La pyramide de test

Vous pouvez classer les tests dans les applications modernes par taille. Les petits tests ne se concentrent que sur une petite partie du code, ce qui les rend rapides et fiables. Les tests volumineux ont une portée étendue et nécessitent des configurations plus complexes, difficiles à gérer. Toutefois, les tests de grande envergure sont plus fidèles* et peuvent détecter beaucoup plus de problèmes en une seule fois.

*Fidélité : fait référence à la similitude de l'environnement d'exécution des tests avec l'environnement de production.

La répartition du nombre de tests par portée est généralement représentée sous la forme d'une pyramide.
Figure 1. La répartition du nombre de tests par portée est généralement représentée sous la forme d'une 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, avec les tests de petite taille, plus nombreux, formant la base et les tests de grande taille, moins nombreux, formant la pointe.

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 l'exemple d'une stratégie potentiellement inefficace. Ici, le nombre de tests par taille ne s'organise pas en pyramide. Il y a trop de grands tests de bout en bout et trop peu de tests d'UI de composants :

Stratégie déséquilibrée où la plupart des tests sont effectués manuellement et les tests d'appareils ne sont exécutés que la nuit.
Figure 2. Stratégie déséquilibrée où la plupart des tests sont effectués manuellement et les tests d'appareils ne sont exécutés que la nuit.

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

Il est important de tenir compte des implications de ce phénomène sur le coût de l'identification et de la correction des bugs, et de comprendre pourquoi il est important d'orienter vos efforts de test vers des tests plus petits et plus fréquents :

  • Lorsqu'un test unitaire détecte un bug, il est généralement corrigé en quelques minutes, ce qui représente un faible coût.
  • Un test de bout en bout peut prendre des jours pour découvrir le même bug. Cela peut attirer plusieurs membres de l'équipe, ce qui réduit la productivité globale et peut retarder une version. Le coût de ce bug est plus élevé.

Cela dit, une stratégie de test inefficace est toujours préférable à l'absence de stratégie. Lorsqu'un bug est détecté en production, il faut parfois des semaines pour que le correctif soit déployé sur les appareils des utilisateurs. La boucle de rétroaction est donc la plus longue et la plus coûteuse.

Une stratégie de test évolutive

La pyramide de 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éfinition précise. Les équipes peuvent donc définir leurs catégories différemment, par exemple en utilisant cinq niveaux :

Pyramide de test à cinq niveaux avec les catégories "Tests unitaires", "Tests de composants", "Tests de fonctionnalités", "Tests d'application" et "Tests de version candidate", dans l'ordre croissant.
Figure 3. Pyramide de test à cinq niveaux.
  • Un test unitaire est exécuté sur la machine hôte et vérifie une seule unité fonctionnelle de logique sans dépendance sur le framework Android.
    • Exemple : Vérifier les erreurs de décalage d'une unité 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 de test d'un test de composant s'étend à des abstractions plus élevées que les méthodes et classes individuelles.
  • Un test de fonctionnalité vérifie l'interaction entre deux composants ou modules indépendants ou plus. Les tests de fonctionnalités sont plus volumineux et plus complexes, et fonctionnent généralement au niveau des fonctionnalités.
  • Un test d'application vérifie la fonctionnalité de l'ensemble de l'application sous la forme d'un binaire déployable. Il s'agit de tests d'intégration à grande échelle qui utilisent un binaire débogable, tel qu'une version de développement pouvant contenir des hooks de test, comme système soumis à test.
    • Exemple : test du comportement de l'UI pour vérifier les modifications de configuration sur un appareil pliable, tests de localisation et d'accessibilité
  • Un test de version candidate permet de vérifier la fonctionnalité d'une version. 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 qui s'exécutent dans un environnement aussi proche que possible de la production sans exposer l'application à des comptes utilisateur ni à des backends publics.

Cette catégorisation tient compte de la fidélité, du temps, de la portée et du niveau d'isolation. Vous pouvez effectuer différents types de tests sur plusieurs couches. Par exemple, le niveau 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

Avant la fusion

Composant

Niveau du module ou du composant

Plusieurs cours ensemble

Non

Local
Robolectric
Émulateur

Débogable

Avant la fusion

Fonctionnalité

Au niveau de la fonctionnalité

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

Simulé

Local
Robolectric
Émulateur
Appareils

Débogable

Avant la fusion

Application

Au niveau de l'application

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

Serveur de simulation
Serveur de préproduction
Serveur de production

Émulateur
Appareils

Débogable

Avant la fusion
Après la 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

Version de production minimisée

Post-fusion
Version préliminaire

Choisir la catégorie de test

En règle générale, vous devez tenir compte du niveau le plus bas de la pyramide qui peut fournir à l'équipe le niveau de commentaires approprié.

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

Sujet testé

Description de ce qui est testé

Catégorie de test

Exemple de type de test

Logique du validateur de formulaire

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

Tests unitaires

Test unitaire JVM local

Comportement de l'UI du formulaire de connexion

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'interface utilisateur du formulaire de connexion

Formulaire suivant une spécification UX

Tests de composants

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

Intégration au gestionnaire d'authentification

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

Tests de fonctionnalités

Test 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'application

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

Parcours utilisateur critique : se connecter

Flux de connexion complet à l'aide d'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, l'appartenance d'un élément à une catégorie ou à une autre peut être subjective. Il peut y avoir d'autres raisons pour lesquelles un test est déplacé vers le haut ou vers le bas, comme le coût de l'infrastructure, l'instabilité et la durée des tests.

Notez que la catégorie de test ne détermine 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 QA effectuent des tests sur les versions candidates, mais elles peuvent également être impliquées dans d'autres étapes. Par exemple, les tests exploratoires pour détecter les bugs dans une fonctionnalité sans script.

Infrastructure de test

Une stratégie de test doit être soutenue par une infrastructure et des outils qui aident les développeurs à exécuter leurs tests en continu et à appliquer des règles garantissant que tous les tests sont réussis.

Vous pouvez catégoriser les tests par portée pour définir quand et où exécuter chaque test. 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é

Local et émulateurs

Avant la fusion ou l'envoi d'une modification

Application

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

Après la fusion, une fois la modification fusionnée ou envoyée

Version candidate

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

Avant la sortie

  • Les tests Unit et Component 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 sont exécutés avant la fusion ou l'envoi d'une modification.
  • Les tests Application s'exécutent après la fusion.
  • Les tests Release Candidate sont exécutés tous les soirs 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 déplacez des tests vers une cadence nocturne, vous pouvez réduire les temps de compilation et de test CI, mais vous pouvez également prolonger la boucle de rétroaction.