Cet article est un extrait du livre sur JUnit
Avant de s’interroger sur la finalité des tests, examinons leur objet : le logiciel. À la base du logiciel fabriqué il y a toujours un besoin client. Cette notion de client est à prendre au sens très large d’utilisateur, qui n’est pas forcément le client au sens commercial. Ce client a besoin de faire quelque chose et le logiciel l’aide à atteindre son but. Le point de départ est donc le besoin client, l’idée, et le point d’arrivée est le logiciel produit.
Dans un monde parfait, tester est une perte de temps.
En effet dans un monde parfait le processus de fabrication d’un logiciel pourrait être décrit ainsi :
Recueillir les besoins ➝ Imaginer ➝ Écrire ➝ Compiler ➝ Exécuter
Dans un monde parfait, l’erreur n’existe pas. Il est donc inutile de tester.
Sauf que nous ne vivons pas dans un monde parfait, et le propre de la vie est de faire des erreurs. Rien de surprenant donc à ce que l’homme en fasse. Sénèque le jeune nous rappelle, il y a déjà longtemps, que l’erreur est humaine, ‘Errare humanum est’. Conscient de cela, l’homme va tester ce qu’il fabrique dans un premier objectif de validation. L’utilité du test est alors de vérifier que ce qui a été fabriqué est conforme à ce qui a été demandé.
Pour être précis, la citation de Sénèque complète retenue par l’histoire est : ‘Errare humanum est perseverare diabolicum’. Si l’erreur est humaine, il est diabolique de persévérer (dans l’erreur). Ainsi, s’il est tolérable de faire des erreurs, il n’est pas acceptable de les reproduire. Les tests prennent alors un nouveau sens qui va au-delà de la validation : ils permettent de s’assurer que les erreurs passées ne seront pas reproduites en vérifiant les points qui ont déjà fait défaut. En matière logicielle, ces tests sont dits de non-régression.
Les erreurs qu’essaient d’éliminer les tests peuvent arriver à n’importe quel moment du processus décrit ci-dessus.
Recueillir les besoins : durant cette phase, le client exprime ses besoins, ses objectifs. Le premier risque est que le client se trompe dans ce qu’il veut. Si cela peut sembler peu probable a priori, la réalité du terrain montre qu’il en est tout autrement. Entre le début et la fin d’un projet, il est rarissime que le client ne change pas sa demande.
Imaginer : cette phase est le propre de l’activité humaine. De toutes les étapes mentionnées ici, c’est la moins automatisable. Les risques d’erreur sont nombreux. Sans être exhaustif, en voici quelques-unes :
- Mauvaise compréhension du besoin.
- Élaboration d’une solution valide sur le papier mais impossible à réaliser.
- Conception d’une solution largement surdimensionnée.
Écrire : cette phase est le prolongement naturel de la précédente. Elle correspond à la structuration de l’idée sous une forme standardisée. Le logiciel peut être écrit directement à l’aide de code ou sous la forme d’une description dans un métalangage. Les erreurs possibles sont encore nombreuses :
- Mauvaise compréhension de l’idée d’un autre.
- Difficultés à formaliser ses propres idées.
- Non respect du standard.
Compiler : cette phase consiste à transformer le code compréhensible par un humain en un langage compréhensible par une machine. Aujourd’hui, cette phase est très automatisée… Par d’autres logiciels écrits selon les mêmes règles, pouvant donc intégrer des erreurs ! Les outils employés étant largement diffusés, le risque devient de plus en plus faible, mais il existe toujours, surtout lorsque le code est soumis à différents environnements :
- Différentes plates-formes d’exécution.
- Différents outils de compilation.
- Diverses bibliothèques de base.
Exécuter : les risques d’erreurs propres à cette phase deviennent de plus en plus faibles, voire négligeables, étant donné les progrès faits en matière d’informatique. Cependant, c’est durant cette phase que toutes les erreurs non corrigées dans les phases précédentes deviendront visibles :
- Un besoin client mal implémenté.
- Un défaut de conformité aboutissant à un plantage.
- Tout autre comportement anormal ayant des conséquences plus ou moins graves ou visibles.
Ainsi, chacune des étapes évoquées comporte un nombre important d’erreurs possibles. Il devient vital pour le projet de les valider car chacune d’entre elles peut faire dévier considérablement de l’objectif. Le test devient alors un outil de contrôle du processus de fabrication du logiciel. Dans une vision logique, le système pourrait être représenté ainsi :
Le test permet une boucle de rétro-action et donne du feedback sur la qualité de ce qui est produit.
Et vous? Pourquoi testez-vous?
Très bon article, merci.
Pour moi, les test sont un outil de design incrémental. (cela demanderait de développer, désolé de rester bref)
En outre, un test peut aider à comprendre l’implémentation du code qui est en lui même la meilleure spécification de ce que fait le logiciel à ce moment-là.
mes 2cts
“les test sont un outil de design incrémental.”
En effet, ils ont également cette vertue dès lors qu’ils sont:
– automatiques;
– utilisés comme colonne vertébrale du développement (approche TDD)
Merci pour le retour.
Salut Benoit !
Merci pour cet article intéressant. La liste que tu dresses des étapes du processus permet d’établir une liste (non exhaustive …) des erreurs possibles, et ta modélisation en système logique permet de voir le rôle central et indispensable des tests : sans eux et sans feedback, le système va forcément dévier de son objectif ! Excellent !
Comme Jean-Philippe, je pense que les tests peuvent aider à la compréhension du code, à condition d’être vraiment bien écrits, bien lisibles comme un roman et des phrases (grâce à du DSL maison). Cette qualité du code de test est trop souvent négligée entrainant une perte de bon nombre d’intérêts des tests, voir leur perte (code mort).
Concernant le design, c’est évident, les tests vont guider la conception, même s’ils ne sont pas automatisables, mais évidemment en mode TDD qui est l’approche la plus facile pour écrire des tests unitaires (qu’on se le dise !). Par contre, question conception, je suis partisan d’avoir une “vision” globale de l’application (découpage en couches, modules, et librairies, rôles et responsabilités fonctionnelles de chaque élément, grandes lignes des choix techniques et technologiques, etc …). Cette architecture de haut niveau est guidée par le besoin client, bien sûr, mais ne doit pas être guidée par les tests unitaires “au jour le jour” …
Outre la non-régression et la compréhension du code (mentionnés ici), pour moi les tests ont très tôt un rôle dans le projet : ils doivent être une traduction du besoin (carnet de produit, spécifications) en exemples codés, et permettent ainsi la détection au plus tôt des bugs qui pourraient coûter le plus cher (cf Scott Ambler) : les erreurs d’évaluation du besoin et les erreurs de conception.
La prise de conscience de l’intérêt des tests, et de leur effet de gain sur les coups, est lente, difficile, mais j’y crois et m’y emploie … 😉
Xavier,
Merci pour ton retour très enrichissant. Quelques éléments me font réagir:
Je suis curieux que tu complètes cette liste.
Je suis entièrement d’accord. Par contre, je ne suis pas certain qu’un DSL soit indispensable.
Je suis tout à fait d’accord. L’étude de couverture des tests permet d’aider dans la démarche d’identification de code de test mort, mais n’est pas toujours suffisante. L’alteration de code en revanche est redoutable sur ce point.
Sur ce coup, j’ai du mal à partager ton point de vue: sans automatisation, je ne vois pas comment les tests peuvent aider dans la conception…
En effet, c’est une possibilité.
C’est une vrai question. Mon expérience m’a montré que c’était possible d’utiliser les tests “au jour le jour” pour guider une conception, mais m’en a également montré les limites: prendre du recul devient vital à un moment ou un autre, mais n’est pas indispensable dès le départ.
C’est pour moi toute la différence entre l’approche top-down ou bottom-up.
Bienvenue au club!
Encore merci pout ton retour.
#++
Salut Benoit !
Liste des erreurs : je voulais simplement dire que les sources d’erreurs peuvent être nombreuses, mais tu bien listé les principales.
Question DSL, tout dépend de que qu’on entend par là. On peut développer une véritable encapsulation du code pour obtenir quasiment des phrases à chaque ligne, un peu comme du code avec Mockito. Mais je voulais évoquer le simple fait de bien nommer ses variables et méthodes, et surtout, ne pas hésiter à extraire le code dans des méthodes aux noms explicites permettant ainsi de se rapprocher de ce concept de “roman et phrases”.
En évoquant la perte du code de test, sa mort, je voulais souligner l’importance à accorder à ce code, au moins autant d’attention que pour le code de production, ainsi que le respect de règles de base pour les test (exemple : respecter la structure préparation-test-vérification), sous peine de difficulté à maintenir le code de test, ce qui amène forcément à sa suppression. C’est alors une perte énorme de l’investissement réalisé, mais malheureusement, trop souvent constaté !
Enfin, pour la conception, bien sûr, il est possible de la faire en s’appuyant sur une approche purement TDD “au jour le jour”. Mais pour que ça marche, il faut des développeurs aguerris, expérimentés, et qui finalement, auront bel et bien une vision à l’esprit, non ? 😉
Xavier
Salut Xavier,
Je suis tout à fait d’accord avec ce point: ce travail de nommage permet de faire émerger les concepts métiers. Je remarque hélas que ce point est souvent sous-estimé. Pourtant c’est ce qui fait la différence à terme entre un code exploitable et maintenable ou pas..
Je suis encore une fois tout à fait d’accord avec toi: le code de test est du code de première classe! Il mérite d’être traité au moins aussi bien que du code de production. Le plus intéressant est que cela ne demande aucun effort: il suffit de calquer l’architecture du code de production pour concevoir le code de test!
C’est mieux avec.
Ceci-dit:
Merci pour ton retour.
#++