Améliorer la testabilité

Cet article donne quelques pistes de réflexion pour faciliter les tests dans le cadre d'une méthode de développement "cycle en V".

2 commentaires Donner une note à l'article (4)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

I-A. De quoi s'agit-il?

Un produit peut être plus ou moins facile à tester. Qui n'a pas été confronté un jour à ce type de problèmes :

  1. Un logiciel sans spécifications ou des spécifications peu fiables : comment écrire le plan de test dans ces conditions ?
  2. Une anomalie est détectée mais il est impossible de localiser le problème.
  3. Une architecture peu adaptée aux tests unitaires.

Cet article a pour objectif d'étudier quels sont les points qui vont faciliter notre travail de testeur. Plusieurs éléments vont contribuer à améliorer la testabilité à différentes phases du processus de test d'un cycle en V.

I-B. Quelques définitions

  1. Tests unitaires: tests de composants logiciels individuels faits par les développeurs.
  2. Tests d'intégration: tests permettant de valider les interfaces entre composants. Ces tests sont généralement effectués par l'équipe de développement ou par une équipe dédiée dans le cas d'une intégration complexe.
  3. Tests système: tests permettant de valider un système intégré par rapport à des exigences spécifiques. Ces tests sont généralement effectués par une équipe de validation dédiée ou par l'équipe de développement. Ces tests couvrent les aspects fonctionnels et non fonctionnels (performance, installation, robustesse, sauvegarde ...)
  4. Recette: processus qui permet au client de valider que la livraison des fonctionnalités développées par le fournisseur, correspond au cahier des charges.

II. Stratégie de test

Deux éléments de la stratégie de test concernent la testabilité :

  1. Quels sont les objectifs qualité de nos tests ?
  2. De quels moyens allons-nous avoir besoin ?

II-A. Quoi ?

Quand on parle de tester un produit le premier élément que nous exigeons est les spécifications fonctionnelles et pourtant il me semble encore plus important dans un premier temps de bien cerner quels sont les objectifs de nos futures campagnes de tests. Ces éléments sont essentiels pour calibrer nos efforts de test dans la bonne direction.
Imaginons que nous devons tester un nouveau service rendu par une interface Web. Nous ne sommes pas les premiers sur ce marché et la concurrence est rude. Il est d'autre part vital pour la société d'obtenir rapidement 5 % de part de marché. Les éléments stratégiques concernant le produit sont donc connus. Ils sont essentiels pour porter les efforts de test au bon endroit.
L'ergonomie doit permettre au client de trouver facilement les services qu'il désire, et les accès au site doivent être rapides sous peine de voir le client se tourner vers un site équivalent.
En plus des tests fonctionnels classiques, il va falloir valider la performance des interfaces et leur ergonomie. Ces tests demandent des compétences particulières qu'il faudra prévoir. À ce stade nous voyons également qu'il faudra un environnement de test et des moyens dédiés. Ces éléments qui seront traités dans le chapitre suivant, contribuent également à la testabilité du produit.

II-B. Comment ?

Nous voulons mettre notre logiciel en situation réelle et simuler tous les scénarios d'utilisation et d'erreur possibles. En règle générale, nous ne travaillons pas dans le monde réel mais dans un environnement simulé qui se rapproche du réel. Nos outils devront :

  1. Être capables de simuler un environnement réel en mode nominal, erreur et charge.
  2. Être fiables c'est-à-dire eux-mêmes testé.

Simuler un environnement externe peut être complexe, ces outils peuvent se trouver sur le marché comme par exemple un simulateur de réseau GSM pour tester un mobile. Dans ce cas, l'outil est connu et testé. Il suffit de faire une étude pour choisir celui qui conviendra le mieux (fonctionnalités offertes, type de test, profil utilisateur). Si nous devons développer ou faire développer notre propre simulateur, tout se complique, il faudra :

  1. Identifier correctement nos besoins en terme de test.
  2. Evaluer la charge et bien planifier.
  3. Tester l'outil.
  4. Prévoir à la première utilisation la possibilité d'anomalie du côté de l'outil.

Quels sont les risques ?

  1. Outil non livré en temps et en heure ce qui va retarder la phase de test.
  2. Impossibilité de tester certains scénarios (en particulier les cas d'erreur) car l'outil n'a pas été correctement spécifié.
  3. Outil non testé qui va rendre le debug très pénible et entraîner des confits entre développeur et utilisateur de l'outil.
  4. Un bon outil va donc faciliter la phase de test, il faudra donc veiller à sa qualité.

Dans le chapitre automatisation j'aborderai la question des outils plus en détails.

V. Conception et codage

Mais le plus efficace est d'intégrer la testabilité lors de la conception. Deux aspects sont à prendre en compte :

  1. La contrôlabilité : la possibilité de contrôler l'état d'un composant en vue de les tester.
  2. L'observabilité : la possibilité d'observer les résultats de test (intermédiaire et final).

V-A. Contrôlabilité

Roy Osherove, dans son article "Achieving And Recognizing Testable Software Designs", propose les règles suivantes pour atteindre cet objectif et améliorer la testabilité :

  1. Des exécutions partielles : pour déboguer une fonctionnalité nous pouvons exécuter de façon partielle une partie des tests unitaires.
  2. Des résultats identiques à chaque exécution : les résultats ne dépendent pas de l'état du système.
  3. Pas de configuration : aucune configuration du système n'est nécessaire pour tourner les tests.
  4. L'ordre des tests n'est pas important : nous pouvons exécuter les tests dans l'ordre que nous voulons.
  5. Temps d'exécution rapide : la masse de tests unitaires est importante, pour accélérer l'exécution, nous évitons par exemple de nous connecter aux bases de données.

Pour respecter ces règles, il faudra mettre en place des règles de codage qui visent à faciliter le test.
Si nous atteignons ces objectifs nous gagnons en "contrôlabilité". Le résultat est la possibilité de tester complètement et simplement le code par morceaux. Un autre point qui améliore la testabilité est de limiter la complexité du code. Il est mesurable par la complexité cyclomatique (lié au nombre d'imbrications). Plus cette mesure est élevée plus le code est source d'erreur. De plus, il augmente le nombre de jeux d'essais qui permettent le test. D'autre part nous allons limiter le nombre de jeux d'essais et augmenter la contrôlabilité. Là encore, ce sont les règles de codage qui vont améliorer la testabilité.

V-B. Observabilité

Voilà nos tests unitaires et d'intégration ont été réalisés et ont permis de déboguer le code, reste à vérifier que notre logiciel a bien le niveau de qualité en terme de fonctionnalités attendues mais aussi en terme de rendu de service (stabilité, sécurité ...). Il nous faut passer aux tests système. Là encore, un certain nombre d'éléments doivent être prévus pour nous aider dans nos tests :

  1. Politique de gestion d'erreur : date, type d'erreur sont des informations qui permettront de déboguer et trouver rapidement la cause en cas d'erreur détectée par les tests.
  2. Politique de log : différents niveaux que l'on peut activer ou désactiver à chaud. Si ces logs doivent être utilisés par le support, il faudra les rendre compréhensibles et accessibles à tous. Ils facilitent le debug et permettent d'analyser les causes d'erreurs.
  3. Connaissance des états internes du système ou des objets : donner les moyens de connaître les états du système, voire de les gérer, vont permettre de mieux contrôler et observer l'évolution du système lors des tests et ainsi de vérifier la consistance du système. Cela peut être fait par l'intermédiaire d'une base de données ou d'API.

Certains outils tels que les émulateurs ou les débuggeurs peuvent être plus ou moins performants en terme de fonctionnalités et vont eux aussi permettre d'améliorer l'observabilité. Dans le développement d'un logiciel embarqué, l'absence d'émulateur peut ralentir considérablement la sortie d'un produit, car le debug se fera à tâtons par l'émission d'hypothèses.

V-C. Dès la conception

La testabilité doit être prise en compte dès la conception du logiciel, car l'architecture peut améliorer grandement ce critère. Il est d'ailleurs très difficile de l'améliorer a posteriori :

  1. Tenter de rajouter des tests unitaires sur un logiciel où cet aspect a été négligé peut se révéler une tâche ardue. Car il est peu probable qu'il ait été conçu de façon modulaire.
  2. De même, mettre à niveau des logs ou de la gestion d'erreur peut s'avérer être une tâche coûteuse mais éventuellement à mettre en balance avec le coût du debug, support et maintenance ainsi que l'image vis-à-vis du client.

III. Plan de test

Pour écrire un plan de test complet, nous aurons besoin des points suivants :

  1. Les spécifications sont connues et leurs évolutions maîtrisées.
  2. Elles sont complètes et abordent tous les aspects (fonctionnels, opérationnels, performance, sécurité, etc. ).

Comment écrire un plan de test si nous ne savons pas quoi vérifier ? Sans exigences définies, comment faire une couverture et être sûr de la complétude de nos tests ?
Néanmoins, si elles sont absentes une technique de test peut pallier ce manque: les tests dits exploratoires : il s'agit d'écrire et d'exécuter les tests en découvrant le logiciel. Ces sessions seront conduites avec des objectifs qualité définis (comme l'usage d'une fonctionnalité, la sécurité...).
Cette technique peut être appliquée si :

  1. Le produit est connu (existe par ailleurs, les testeurs connaissent le métier).
  2. Les testeurs ne sont pas des débutants.
  3. Les conséquences des erreurs sont minimes (données sensibles, contrat, sécurité des personnes).

Néanmoins, il faut rester conscient que certains points peuvent échapper au testeur, car il n'a pas la maîtrise des spécifications de façon complète et sûre. De plus, tout est sérialisé, ce qui augmente les délais de livraison.

IV. Exécution des tests

IV-A. Tests Unitaires

Les tests unitaires sont essentiels pour améliorer la testabilité :

  1. Ils vont faciliter le déroulement des tests système : les anomalies liées à des erreurs de codage sont plus facilement détectées et corrigées à ce niveau.
  2. Lors d'évolution ou de correction, ils vont permettre de localiser les erreurs de façon fine et permettre de corriger des anomalies ou d'améliorer le code sans crainte de non régression.

IV-B. Tests d'intégration

Il est absolument nécessaire de planifier ces tests avec tous les intervenants avant de commencer les tests systèmes. Si cette phase n'est pas réalisée, au lieu de tester les aspects fonctionnels les testeurs feront face à un système qui ne fonctionne pas. Ils devront faire appel à tous les intervenants pour déboguer le système petit à petit. Ils prendront du retard et des conflits vont apparaître.
Une phase d'intégration planifiée et menée à terme facilite les tests système.

IV-C. Automatisation

Tester prend du temps. Quand le nombre de test devient trop important, les versions à tester se multiplient l'automatisation est la solution qui s'impose. Sinon faute de temps nous allons procéder à des coupes dans les tests pour livrer en temps et en heure et prendre des risques sur la qualité du livrable. L'automatisation est donc nécessaire mais encore faut-il si prendre à l'avance. Le schéma suivant résume l'outillage nécessaire et nos choix vont augmenter ou non la testabilité de l'application de par les possibilités de nos outils:
Image non disponible
Injecteur : l'outillage qui va permettre d'appeler les interfaces externes de l'application comme par exemple des scripts de test écrit pour des API (java, C++, Soap) ou des tests automatiques d'interfaces (QTP, WINRUNNER, Selenium ...).
Simulateur : outil qui simule le monde extérieur. Si l'application communique avec d'autres applications il faut simuler la communication entre ces applications. Si le monde extérieur se réduit aux utilisateurs finaux il n'y a pas besoin de simulateur on utilise le même outil que l'injecteur soit des outils d'automatisation de test d'interfaces.
Vérification : toutes les vérifications qui vont déterminer si le test est réussi ou non.
Outil de gestion d'exécution des tests : le maître d'oeuvre qui va séquencer les autres outils et centraliser les résultats de test.

L'injecteur est la partie la moins complexe, sauf pour les tests de performance. Pour ces tests il faudra également prévoir le hardware nécessaire. L'autre difficulté est d'être capable de simuler une utilisation réaliste de l'application.
La partie vérification : pour automatiser il faut pouvoir accéder à certaines données qui sont les sorties, mais parfois à des données internes en base ou à l'état d'un service en cours. Peut-être faudra-t-il faire développer des APIs internes?
Les résultats devront être déterministes. Lors d'un de mes projets nous avions réalisé un système d'automatisation de la façon suivante: le test était exécuté manuellement une première fois; le résultat après vérification était enregistré comme référence; le test automatisé vérifiait la sortie par rapport à la référence. Malheureusement il s'agissait d'une liste de contacts et à la version suivante l'ordre des contacts n'était plus déterministe. Impossible de repasser les tests automatiques même en changeant les résultats. Nous avons demandé une relivraison car c'était plus facile côté développement.
Autre exemple : pour utiliser Sélénium (outil d'automatisation d'interfaces web) les objets graphiques doivent être identifiés par un identifiant unique, qui doit être positionné par le développeur d'application web.
Côté simulateur il faut qu'il réponde aux besoins de test c'est à dire être capable de simuler tous les cas de figures, cas normaux et cas d'erreur. La meilleure solution est de scripter le comportement du monde extérieur.
Image non disponible

Ici la conception du simulateur est très simple, il consiste essentiellement à envoyer et recevoir des messages décrits dans les scripts, gérer des expirations de délai, vérifier que le message reçu est conforme à celui du script et fournir un compte rendu d'exécution du script. Aucune interprétation du message n'est demandée dans ce cas.
La plus mauvaise solution serait de simuler avec du code le comportement du monde extérieur. C'est à dire que le simulateur va interpréter les messages qu'il reçoit et répondre en fonction. Dans ce cas le développement du simulateur va être aussi complexe que celui de l'application et poser les problèmes décrits dans le chapitre II-B. Il y a également de fortes chances que l'on ne pourra pas tester de façon exhaustive les fonctionnements inattendus ou les expirations de délais.
Le choix des outils de manière générale est crucial et peut réduire considérablement le nombre de cas de tests ou les rendre difficilement réalisables, je pense en particulier aux cas d'erreur.
De même travailler en amont en intégrant lors de la conception des exigences relatives au test, est nécessaire (par exemple des API internes qui permettraient de vérifier des états de transaction) et facilite les activités de test. Cela est aussi vrai pour les activités de maintenance.

VI. Planification

La livraison des fonctionnalités dans le temps peut également jouer. Par exemple dans le cadre d'un développement itératif, les fonctionnalités de création d'abonnés ont été livrées mais pas celles de suppression. Après un test de création pour détruire les données, il faut à chaque fois arrêter le serveur, détruire en base et redémarrer le serveur tout en se synchronisant avec les personnes utilisant ce même serveur. Compliqué, il eût été plus simple de livrer directement les fonctions de suppression.
De même, planifier la livraison des corrections d'anomalie peut faciliter l'exécution des tests surtout en cas d'anomalie bloquante.

VII. Conclusion

La testabilité se joue dès les débuts de la réalisation de l'application à toutes les étapes (définition de la stratégie de test, spécifications, conception ...). Le test et le développement doivent donc travailler ensemble avec comme objectif rendre l'application testable. Les méthodes agiles facilitent la gestion de cette problématique (écriture des tests avant le codage, participation des utilisateurs finaux, équipe projet mixte etc ...) mais je ne saurais en dire plus n'étant pas du tout une spécialiste des de ces méthodes.
Tous ces pré requis sont nécessaires pour tester dans les meilleures conditions :

  1. Des objectifs, des risques identifiés orientent nos tests.
  2. Sans spécifications, pas de références pour nos oracles de test.
  3. sans réel moyen de test l'efficacité des tests est diminuée ou bien les tests sont fastidieux à faire, etc.
  4. Un logiciel testable va de pair avec une bonne conception basée sur les principes de modularité, etc.
  5. Respecter les différentes phases de test (unitaires, intégration et système).
  6. Les outils de tests livrés à temps et correctement testés.
  7. Des logs et une gestion d'erreur facilitent le debug.
  8. Planification des livraisons en prenant en compte les besoins des tests.

Évaluer la testabilité et mettre en amont les moyens pour l'améliorer sont donc nécessaires à la bonne exécution de l'activité de test. Cela a un coût, nécessite de la rigueur et du travail en amont, mais accélère les phases de test.

VIII. Références, liens

Achieving And Recognizing Testable Software Designs Part I
Roy Osherove
le forum Tests
les articles et tutoriels sur les tests

IX. Remerciements

Tous mes remerciements à Wachter pour sa relecture et ses corrections orthographiques. Tous mes remerciements à Ricky81 pour sa relecture et ses conseils et commentaires.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2009 Dominique Mereaux. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.