JeuWeb - Crée ton jeu par navigateur
L'utilité du test unitaire - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : L'utilité du test unitaire (/showthread.php?tid=6973)

Pages : 1 2 3 4 5 6


RE: L'utilité du test unitaire - Xenos - 26-08-2013

Citation :si tu la change (la preuve) tu la refais. Mais comme l'a dit Sephi c'est un outil.
Dans ce cas, ok. Donc le test unitaire n'est utile que si la preuve faite a besoin d'une hypothèse type "ça doit marcher dans tel cas précis". Par exemple, si ma preuve est "quelque soit l'entier en entrée et si mult(0, _) = 0 alors mult(X,Y)=X*Y", je peux effectivement recourir au test unitaire pour prouver que mult(0,_)=0 (même si ce n'est pas forcément obligatoire à mon avis, puisque l'étude du code source de la fonction permet de le dire directement).
Si ma preuve n'en n'a pas besoin (si, par exemple, elle dit "quelque soit l'entier en entrée j'ai son carré en sortie"), alors je n'ai pas l'utilité du test unitaire pour la preuve, mais ça pourrait me servir comme documentation (à titre d'exemple d'utilisation de la classe), c'est cela?

Ok, dans un tel cas, je comprend l'intérêt. Mais je ne le ressens toujours pas :p C'est comme comprendre l'intérêt d'une voiture qui fonce à 300Km/h sans en avoir envie.

Si je suis d'accord avec Niahoo, je ne le suis pas avec Roworll. Ok, le test unitaire ne dit pas qu'un soft est "bugproof", parce que le test unitaire ne test qu'un cas... Mais alors, pourquoi serait-ce "un sanity check complet" ou "tu es sur que les méthodes fonctionnent."? Ca contredis même pas la phrase précédente, mais le début de la même phrase "Ça ne permet évidemment pas de faire un code 100% bug free"... Ou alors, j'ai mal compris?

Si tu proposes la valeur 2 en entrée, ok, ca ne passe pas le test. Donc, je modifie ma méthode, et j'ajoute un "if $x == 2 then return 4;"...
Ok, le test unitaire valide une situation donnée, mais une et une seule. Pas une situation générique.
Et il me semble que oui, on peut "tester" tous les cas. C'est pour cette raison que la plupart des preuves (en maths, en logique, ou en n'importe quoi) commencent par "quelque soit/Pour tout X respectant les conditions suivantes". On ne vérifie par chaque valeur de X, chaque situation, mais on travaille sur le plan large: on ne teste non pas par valeur/situation, mais par hypothèse.

Donc, ok, le test unitaire valide le comportement pour une situation très précise. Mais alors, quels outils permettraient de valider toutes les situations envisageables (en considérant quelques axiomes, pas au niveau de son propre code mais plutôt au niveau hardware/langage)?


RE: L'utilité du test unitaire - SorenS - 26-08-2013

Le test unitaire test une fonction. Et là où c'est intéressant c'est lorsque ton projet grossis tu es toujours sur du fonctionnement de tes fonctions.

Si ta fonction "carré" (bien testée) valide son test mais que tu as un comportement bizarre, tu sais déjà que le soucis n'est pas là. Dans ton code, les fonctions communiquent entre elles et si tu ne tests pas unitairement, comment savoir si tout est ok quand tu mets des couches et des surcouches ?


RE: L'utilité du test unitaire - Xenos - 26-08-2013

Je suis d'accord avec le principe de "valider une unité de code", ce n'est pas quelque chose que je remet en question.

Ce que je remet en question, c'est le fait de passer par des tests unitaires, qui ne vérifient qu'une seule et unique situation, pour en induire que toute la fonction marche. "Bien testé", ça veut dire quoi? En termes logiques, cela veut dire "valide pour toutes les entrées qu'elle pourra accepter", et les tests unitaires ne font pas cela, puisqu'ils ne testent qu'un lot précis d'entrées...

A mon sens, il vaut mieux faire la preuve "papier", à partir du code source de la fonction. Ainsi, on pourra dire "cette fonction marche pour toute entrée acceptable, en voici la preuve", et à partir de là on pourra l'utiliser. Mais dire "cette fonction passe mes tests unitaires donc elle marche", c'est péremptoire...

Pour la partie "preuve de fonctionnement, pas pour la documentation, le test unitaire me semble être totalement insipide et inutile, puisqu'on a besoin non pas d'un test, mais d'une preuve unitaire. Et le test unitaire tel que présenté ainsi me semble donc inutile dans le cadre de la preuve de fonctionnement d'un bout de code (ne serait-ce que le principe de "prouver de l'extérieur d'une classe que l'intérieur de cette classe marche" devrait choquer).


RE: L'utilité du test unitaire - niahoo - 26-08-2013

Le test ne fait pas partie de la preuve. La preuve permet de s'assurer que les tests des cas de base assurent la validité d'éventuels tests pour toutes les valeurs possible. Dans ce cas tu ne peux plus dire "qu'ils ne testent qu'un lot précis d'entrées...".

Maintenant, beaucoup de fonctions ne peuvent pas être prouvées correctes : basiquement si tu utilises this c'est mort déjà. C'est pour ça qu'en OO les tests ne servent qu'à s'assurer qu'un objet d'une classe se comporte plus ou moins comme on le souhaite. (mais tu peux faire de l'OO avec plein de fonctions pures aussi).

Mais généralement c'est déjà un bénéfice énorme.

Un sanity check complet ça veut dire que tu reteste toute la base de code à chaque fois que tu fais une modif. ça veut dire par exemple que tu t'assure que tes modifs n'ont pas de répercussion causant une régression sur un volet totalement différent de ton appli, ça ce sont les bug les plus chiants mais généralement si on est propre il n'y en a pas.

Ou si tu as touché à plein d'endroits dans le code tu valides plein de modifs d'un coup.

Ou bien tu as accepté un commit et tu vérifies qu'il fout pas la grouille.

Ou bien tu testes sur 40 plateformes/VM différentes et tu as la flemme de faire toutes tes VM à la main et ne dormir que deux minutes par nuit.

ça te permet aussi de fournir le test pour une interface. Par exemple en erlang l'interface dict permet de créer des modules gérant des hashmaps : tu as dict, orddict, gb_tree, rb_tree. Selon les données, leur quantité, tu as des modules de plus en plus spécialisés. Et quand tu veux en créer un nouveau, tu prends la doc de l'interface + le test afin de valider rapidement ton code.

D'ailleurs en OO ce sont les interfaces qui sont testées au final. Beaucoup sont partisans de ne pas tester les méthodes privates.

(26-08-2013, 02:06 PM)Sephi-Chan a écrit : Ils me servent à documenter ce que je veux obtenir avant d'avoir écrit l'implémentation de cette chose.
Ça permet donc de piloter la conception de mon code et de détecter des régressions introduites par un refactoring.

Perso c'est ça qui me plaît le plus : j'ai pour habitude d'écrire du code en utilisant une API qui n'existe pas encore. ça permet de voir ce qui est pratique ou non, ce qui est relou à répéter, à réécrire, les endroits où on veut pouvoir spécifier des options, etc.

ça te rappelle quelque chose ? Et oui, tout le monde fait ça, et pourquoi ? parce qu'il faut bien tester ce qu'on écrit, au moins une fois, afin de voir si on suit le bon chemin.

Le TDD c'est simplement l'automatisation et la systématisation de ça sur l'ensemble de l'appli pour multiplier les bénéfices d'une simple tâche que tous les développeurs font : lancer son code.

Donc je ne vois pas pourquoi s'en passer, et rien ne t'oblige à avoir une couverture à 100% ni d'utiliser aussi des preuves mathématiques quand tu le peux. (mais perso en FP la preuve mathématique à souvent la même gueule que le code lui-même donc c'est relou)


RE: L'utilité du test unitaire - Xenos - 26-08-2013

Ok, ça facilite le test d'une situation donnée, d'accord.
Donc, le test unitaire ne sert que si la preuve dit "la fonction est prouvée si et seulement si les tests de base passent". Il me semble quand même malvenu de faire ce test en dehors du code-même de la fonction incriminée... Surtout si on doit se retaper la preuve entière à chaque modification !

Pourquoi "this" tuerait la preuve? Supposons le cas d'un langage type PHP: pas de pointeurs (et pas de traitements parallèles, mais ce dernier point pourrait être omis je pense), alors pourquoi "this" tuerait toute tentative de preuve formelle?


RE: L'utilité du test unitaire - niahoo - 26-08-2013

(26-08-2013, 05:21 PM)Xenos a écrit : Ok, ça facilite le test d'une situation donnée, d'accord.
Donc, le test unitaire ne sert que si la preuve dit "la fonction est prouvée si et seulement si les tests de base passent". Il me semble quand même malvenu de faire ce test en dehors du code-même de la fonction incriminée... Surtout si on doit se retaper la preuve entière à chaque modification !

Non, le test sert dans tous les cas. On va pas s'amuser à prouver des fonctions qui affichent une vue en chargeant un fichier sur le disque dur, en assignant les variables et en balançant le tout sur la socket ouverte.

Ensuite tu veux dire que tu voudrais mettre les tests d'une fonction DANS la fonction ?

(26-08-2013, 05:21 PM)Xenos a écrit : Pourquoi "this" tuerait la preuve? Supposons le cas d'un langage type PHP: pas de pointeurs (et pas de traitements parallèles, mais ce dernier point pourrait être omis je pense), alors pourquoi "this" tuerait toute tentative de preuve formelle?

Hmmm en fait ça doit bien être possible de prouver qu'une fonction est correcte seulement comme this est mutable, il se peut que la même méthode appelée plusieurs fois avec les mêmes arguments renvoie une chose différente à chaque fois :

Code :
class truc {
    set (x) {
        this.x = x
    }
    plus(y) {
        return this.x + y
    }
}


t = new truc
t.set(0)
assertEqual(t.plus(3), 3)
t.set(3)
assertEqual(t.plus(3), 6)
t.set(4)
assertEqual(t.plus(3), 7)

Du coup t'as l'air malin pour prouver mathématiquement que c'est correct, il n'y a plus rien à prouver... Du coup le test ci-dessus reste utile pour s'assurer que la classe se comporte bien comme on veut.

assertEqual(t.plus($_GET['machin']), WFT? )



RE: L'utilité du test unitaire - Ter Rowan - 26-08-2013

pour donner ma vision :

la stratégie de tests, au sens large, doit se baser avant tout sur le bénéfice risque

- développer des tests unitaires automatisés n'a de sens que si on les réutilise souvent (donc il faut avoir souvent des évolutions mais il ne faut pas tout changer à chaque fois). En effet, si on ne doit tester qu'une fois une appli car si elle, et son environnement (données, connexions, ...), sont tellement stables qu'il n'y a aucune activité pendant des années, le coût sera bien trop important par rapport à des tests manuels (le temps de conception des tests est le même, qu'ils soient manuels ou automatisés, le temps de dev est bien plus élevé que le temps d'une passe de tests) A l'inverse, si l'application est tellement fluctuante que les tests doivent être fortement redéveloppés à chaque fois à quoi bon.

- développer des tests unitaires automatisés avec l'objectif de couvrir 100% des fonctionnalités et l'exhaustivité des cas est aussi une perte de temps

- accepter des trous dans la raquette des tests n'est pas une hérésie. On peut aussi s'appuyer sur l'apprentissage. Un bug est détecté (soit en phase de recette, soit en production) alors que les tests unitaires n'ont rien vu alors on apprend en rajoutant un test correspondant aux entrants du bug. Ex pour le cas de la multiplication :

je teste : 1 * 1 ca donne 1, 1 * 2 ca donne 2, 2 * 3 ca donne 6, ca a l'air correct, youpi. Mais voilà, suite à un bug identifié plus tard dans la chaine de validation, je m'aperçois que 8 * 4 ne donne pas 32 mais 3 (ce con ne renvoie que le premier chiffre de la multiplication et pas tout le résultat) qu'à cela ne tienne, je rajoute un cas test 8 * 4, j'ai appris, la prochaine phase de tests automatisé identifiera ce bug éventuel (en cas de régression)


bref les tests unitaires ou pas, automatisés ou pas, n'ont pas systématiquement pour vocation de garantir et d'apporter la preuve que 100% du développement répond correctement au besoin. Ils sont là pour améliorer la qualité en réduisant les coûts liés à l'anomalie éventuelle

(évidemment un logiciel de paie, surtout pour ma paie, mérite d'être extrêmement bien testé :p )


RE: L'utilité du test unitaire - Xenos - 26-08-2013

Nan, je voulais dire qu'il me semble malvenu de vouloir faire les tests sans tenir compte du code source de la fonction, en dehors de toute considération du code source.
Je ne vois pas en quoi l'écriture sur un disque ou l'ouverture d'un socket ne pourrait pas être prouvée. Si les fonction utilisées dans le code courant sont prouvées, alors il suffit de prouver le code courant, ce qui est faisable.

La preuve de ton exemple est bien mathématique (en supposant que le langage lui-même soit fiable):
set() assigne à x la valeur passée en paramètre, et plus() retourne le résultat de l'opérateur + sur la valeur de x et sur l'argument y.
Ma preuve mathématique va te dire:
Citation :Quelque soit x, set(x) assigne x à l'attribut this.x.
Queque soit y, plus(y) retourne le résultat de l'opérateur + appliqué sur x, quelconque par définition, et y, quelconque aussi.

C'est un biais cognitif que de se dire "plus() renvoie y+x" (faut pas faire de la traduction mot-à-mot du code source).
Si je veux que "set() assigne un nombre x à this.x", alors il faut que je change ma méthode set(): elle ne respecte pas la spécification.
Si ma spécification dit "plus() doit ajouter le nombre entier y à this.x (définit comme nombre entier aussi, plus haut dans la spec)", alors mon implémentation de plus() n'est pas correcte. Si jamais set() était codée correctement, avec un typage set(int x), alors je pourrai dire "je sais que this.x est un entier quelconque", et j'affine un peu ma preuve sur plus() ("plus() renvoie le résultat de l'opérateur + appliqué à x entier et à y quelconque).

D'ailleurs, je ne comprends pas bien l'exemple car justement, les trois tests que tu as effectués sont validés, mais la fonction plus() ne réalise pas ce que la spécification lui demandait (ajouter deux entiers).
Y'a un coté cygne noir à la preuve par les tests unitaires: on ne prouve pas que la méthode marche dans l'absolu, on prouve juste qu'il y a quelques cas où ça marche.
La preuve "mathématique", formelle, ou brutale si on veut, va elle prouver que le code marche bel et bien quelque soit le cas envisageable.

Citation :Mais voilà, suite à un bug identifié plus tard dans la chaine de validation, je m'aperçois que 8 * 4 ne donne pas 32 mais 3
Avec une approche par la preuve formelle, tu l'aurais vu avant qu'il n'arrive, car dans ta preuve mathématique (oui, à condition de savoir faire de la vraie logique et pas de la mélasse), tu aurais vu apparaitre un "limité à l'intervalle 0..9" (typique du débordement d'entier).
L'approche par le test me semble malsaine comme dans cet exemple car les tests passent, mais "plus tard", ça va te tomber dans le coin de la figure, et c'est généralement là que démarrent les bidouillages et arrangements boiteux qui finissent par massacrer un projet (non?).

Ok, le test qui couvre 100% du possible me semble... impossible aussi XD Mais la preuve qui couvre 100% du possible, elle est possible justement (avec quelques axiomes quand même, tu type "l'ordinateur sait compter" ou "le langage est fiable").


RE: L'utilité du test unitaire - Ter Rowan - 26-08-2013

ton argumentaire ne peut pas me convaincre car j'ai une méconnaissance sur un terme que tu utilises : qu'entends tu par "preuve" ?

d'autres parts, as tu des exemples concrets, dans des entreprises, où ce système de "preuve" à justement fait ses preuves ? (la question est peut être con, fonction de ta première réponse)

ce sujet m'intéresse car nous avons eu dans ma boîte un vrai sujet sur les tests (conclusions pas très convaincantes, et décisions assez médiocres)


RE: L'utilité du test unitaire - niahoo - 26-08-2013

pfff encore une fois les tests ne sont pas là pour prouver (au sens math/logique) qu'un code fonctionne ... Si tu faisais cette preuve pour chaque fonction tu n'aurais pas besoin de tests mais tu n'aurais pas de clients non plus. Les tests permettent de valider une spec, de documenter du code et enfin de tester rapidement une implémentation. Les tests sont subjectifs, il sont écrits par un humain qui sait ce qu'une API doit faire. L'implémentation est faite pour répondre à un besoin et pas seulement pour valider le test. Mais quand les tests sont bien conçus, les satisfaire satisfait aussi le besoin.

Pour mon code, ta preuve n'est pas recevable .. il peut se passer des trucs entre 'set()' et 'plus()' vu que this est mutable. Mon exemple est pas terrible car prouver plus() revient à prouver (+) ... Bon et tu as raison, si le langage est bien implémenté, que this ne peut pas être appellé par autre chose que le thread courant tu peux prouver qu'un appel à set() suivi d'un appel à plus() peut être atomique mais ça sembe bien relou

Citation :mais la fonction plus() ne réalise pas ce que la spécification lui demandait (ajouter deux entiers).
Ah bon !?! de quelle spec tu parles Óò

En PHP quand tu fais "$a = 3; 3 + 5;" $a ne vaut pas 8 par exemple. un langage qui ferait ça serait ultra chiant

Ter Rowan : Un exemple de preuve pour un algorithme de tri (insertion sort ou insert sort je sais plus comment il s'appelle) : http://www.cs.cornell.edu/courses/cs3110/2011sp/recitations/rec12.htm