A propos des tests unitaires automatisés

18 Jan 2013 26 ,

Les tests unitaires automatisés, c’est quoi ?

Les tests unitaires automatisés (TUA) sont une des composantes de l’outillage permettant de valider qu’une application répond aux exigences suivantes :

  • Est-ce que mon application fonctionne ?
  • Est-ce que mon application est stable ?
  • Est-ce que mon application couvre mon besoin ?
Les TUA ne répondent à aucune de ces questions mais assurent le premier niveau de test qui permet de valider qu’un composant fonctionne unitairement.

Il existe différents tests : unitaires, d’intégration, fonctionnels. Chaque type de test est complémentaire et couvre un périmètre donné. Les tests unitaires sont la plus petite unité de test possible : il valide le bon fonctionnement d’un composant, d’une classe ou d’une fonction.

De manière très concrète, les TUA sont des fonctions ou des classes qui testent le résultat d’une fonction ou d’une classe en passant des données en entrée et en comparant les données en sortie. Si, en passant A j’obtiens B alors que j’attendais C, le test est faux. Au contraire, si en passant A j’obtiens C, le test est vrai.

Les tests sont donc faciles à écrire mais on entrevoit aisément la complexité d’écrire des tests *pertinents* et la complexité de gérer un jeu de données cohérents.

Quels avantages ?

Pouvoir tester à l’infini et facilement son code

Les tests sont travailleurs : qu’on les joue une ou mille fois, ils seront toujours d’accord et feront toujours leur travail de la même manière. Ils sont ainsi forts utiles dès qu’on ajoute, modifie ou supprime des fonctionnalités. Cela permet d’effectuer à minima une validation de non-régression (VNR).

Garantir le fonctionnement des briques logicielles de manière unitaire

Un test valide un fonctionnement. En listant tous les fonctionnements, il est possible de les couvrir complètement à l’aide de tests unitaires. En pratique, la couverture des tests automatisés est rarement complète (sauf pour les librairies distribuées). Cette couverture est doublement intéressante :

  1. pour vérifier qu’un composant répond (qu’il réponde vrai ou faux)
  2. pour vérifier qu’un composant répond de manière correcte
Toute la valeur ajouté des tests unitaires réside dans le choix des tests implémentés. Il est en effet intéressant de tester du code complexe alors que tester du code simple est généralement une perte de temps.

Du code facile à écrire

Le troisième avantage des TUA est qu’ils sont faciles à écrire. Tout programmeur sait écrire un test. C’est, en définitive, aussi simple à écrire que la fonctionnalité elle-même. C’est ainsi que sont apparues de nouvelles méthodes de développements : les tests écrit avant le développement ou TDD (Test Driven Development). Le concept est le suivant : puisque je connais les fonctions et méthodes à implémenter, pourquoi ne pas d’abord écrire le test qui valide le fonctionnement pour ensuite écrire le fonctionnement lui-même ? J’y reviendrai dans un article dédié.

Quels inconvénients ?

Malheureusement, tout n’est pas si simple. Les tests automatisés ne sont pas la solution ultime aux bugs. La mise en place de TUA nécessitent en réalité des contreparties dont vous n’êtes peut être pas prêts à payer le prix…

Croire que des tests OK garantissent une application stable

C’est là une erreur majeure. Les tests automatisés garantissent uniquement les composants testés unitairement. Il y a plusieurs choses qui ne sont pas pris en compte :

  • il ne peut pas y avoir 100% du périmètre fonctionnel couvert par les tests automatisés. Pour des applications métiers, il est impossible d’écrire les tests correspondant à tous les fonctionnements. Imaginez testez toutes les structures conditionnelles imbriquées, toutes les fonctions, tous les enchaînements possibles…
  • les TUA ne garantissent pas que les composants fonctionnent ensemble. Un code couvert à 100% par des TUA ne donnera pas forcément une application fonctionnelle. Ce niveau de test est pris en charge par les tests d’intégration : ils valident le fait que les composants fonctionnent correctement ensemble.
  • les TUA ne garantissent pas que l’application répond aux besoins. C’est le rôle des tests fonctionnels.

Ecrire des tests qui ne servent à rien

Ecrire un TUA est tellement simple qu’on oublie parfois leur objectif : apporter une validation sur un fonctionnement complexe ou sensible. Couvrir de tests une classe contenant des calculs simples n’apportent aucune valeur ajoutée et est donc une perte de temps.

[php]class Math {
public function somme($a, $b) {
return $a + $b;
}
}[/php]

Si vous écrivez un test unitaire automatisé pour cette fonction : vous n’avez rien compris.

Maintenir le jeu de données (ou les fixtures)

Des tests sans données, c’est comme un parfum sans odeur : ça ne sert à rien. Les données et leur maintenance sont une des difficultés majeures des tests unitaires. La création du jeu de données initial est souvent complexe et fastidieuse. Par ailleurs, à chaque évolution du modèle de données, il faudra mettre à jour l’application, les tests unitaires *et* le jeu de données.

Ces éléments font que la maintenance des tests unitaires et fixtures prend du temps et constitue une charge supplémentaire.

Coût d’écriture et de maintenance

Bien que difficile à chiffrer, le surcoût lié à l’écriture des TUA est important. Celui lié à leur maintenance l’est encore plus ! Le problème des tests est qu’ils nécessitent un investissement qui ne peut être récupéré qu’à long terme. Je considère qu’il représente à minima un surcoût de 20% des charges de développements sur des projets à engagement.

Ainsi, les tests automatisés sont à réserver à des domaines complexes ou sensibles. A titre d’exemple, les ajouts de TUA sur un moteur de calcul ou sur des fonctionnalités de paiement seront pertinents.

Comment fait-on du test unitaire en PHP ?

Vous trouverez sur le Web de nombreux frameworks gratuits permettant de tester unitairement votre code PHP. Je vous propose ici une petite liste qui n’est pas exhaustive mais qui couvrira tous vos besoins :

Exemple :
[php]require_once(‘simpletest/autorun.php’);
require_once(‘../classes/log.php’);

class TestOfLogging extends UnitTestCase {
function testLogCreatesNewFileOnFirstMessage(){
@unlink(‘/temp/test.log’);
$log = new Log(‘/temp/test.log’);
$this->assertFalse(file_exists(‘/temp/test.log’));
$log->message(‘Should write this to a file’);
$this->assertTrue(file_exists(‘/temp/test.log’));
}
}
[/php]

Exemple :
[php] class PileTest extends PHPUnit_Framework_TestCase {
public function testerPushEtPop() {
$pile = array(); $this->assertEquals(0, count($pile));

array_push($pile, ‘foo’);
$this->assertEquals(‘foo’, $pile[count($pile)-1]);
$this->assertEquals(1, count($pile));

$this->assertEquals(‘foo’, array_pop($pile));
$this->assertEquals(0, count($pile));
}
}
[/php]

Exemple :
[php]namespace vendor\project\tests\units;

require_once ‘path/to/mageekguy.atoum.phar’;

include ‘path/to/project/classes/helloWorld.php’;

use \mageekguy\atoum;
use \vendor\project;

class helloWorld extends atoum\test
{
public function testSay()
{
$helloWorld = new project\helloWorld();

$this->string($helloWorld->say())->isEqualTo(‘Hello World!’)
;
}
}[/php]

Conclusion

Les tests unitaires automatisés sont un excellent moyen de tester des composants et de valider leur bon fonctionnement. Ils ne remplacent ni les tests unitaires manuels ni les tests d’intégration ni les tests fonctionnels mais permettent d’en alléger les charges et d’assurer un premier niveau de tests faciles à écrire et facile à rejouer.

Leur utilisation entraîne des contreparties, notamment des surcoûts des développements. Ainsi, il convient de bien réfléchir leur utilisation et de les réserver à des contextes nécessitant des taux de retours très faibles.

26 Comments
  • Julien
    septembre 11, 2019

    Très bel échange pour ce billet !
    Mais je pense que si l’auteur aurai regardé un peu plus avec qui il dialoguait (mageekguy, un modeste contributeur sur atoum :p), l’échange aurait été certainement moins drôle.

  • Julien
    février 25, 2013

    Très bel échange pour ce billet !
    Mais je pense que si l’auteur aurai regardé un peu plus avec qui il dialoguait (mageekguy, un modeste contributeur sur atoum :p), l’échange aurait été certainement moins drôle.

  • Ivan Enderlin
    septembre 11, 2019

    Bonjour,

    Je voulais apporter quelques précisions car il y a des amalgames.

    Il y a une matrice des tests. Sur un axe (appelons-le « type ») nous avons les tests unitaires, fonctionnels etc., et sur l’autre axe (appelons-le « mode d’exécution ») nous avons les tests manuels, automatisés, automatiques etc. Attention, il y a une différence entre automatisé et automatique, j’y reviens.

    Premier axe. Le test unitaire permet de valider atomiquement un logiciel (j’en profite pour préciser qu’il faut bien parler de « validation » et pas de « vérification », c’est très différent). Atomiquement ça veut dire qu’on valide les briques logiciels les plus petites, soit les fonctions ou méthodes. Ensuite, nous avons le test fonctionnel, qui peut aussi s’appeler le test d’intégration (c’est un abus de langage). L’objectif est bien de valider le fonctionnement de plusieurs briques entre elles, mais on reste au niveau du code. Il existe aussi le test fonctionnel à un niveau plus haut, quand on parle de fonctionnalités voulues par le client par exemple. Peu importe, le but est de tester une fonctionnalité, peu importe laquelle et le niveau. Bref, c’est pas mon propos.

    Second axe. Le test manuel, automatisé etc. Manuel ça veut dire qu’on va prendre notre souris et exécuter le test à la main, comme à l’époque : j’exécute ma fonction somme(2, 3) et je regarde que le résultat est 5, super, je coche vert dans mon tableau. Automatisé ça veut dire qu’on va faire exécuter les tests par une machine, à l’aide d’outils appelés frameworks de tests, comme atoum & consors. Ces outils permettent de dire : on va exécuter somme(2, 3), le résultat attendu est 5, et produit moi un rapport au passage.
    Le processus et la démarche sont strictement identiques ! Il n’y a aucune différence, sauf qu’on gagne beaucoup de temps et énormément d’argent car on peut les ré-exécuter autant que l’on le souhaite (comme tu l’as très bien souligné).

    Pour montrer les deux axes : le test unitaire peut être automatisé tout comme le test fonctionnel. Tu dis l’inverse dans ton article.

    Je vais un peu plus loin.

    Quel intérêt d’écrire un test ? Un test est une spécification. Il spécifie comment le code doit se comporter : je donne 2 et 3 à somme, il me calcule 5. C’est très léger comme spécification, car on ne dit pas comment la fonction somme se comporte avec 0 et -1 par exemple, mais c’est toujours ça ! La spécification est censée expliquer le comportement attendu d’une fonction : un bon comportement.

    Mais attention, les tests ne sont pas exhaustifs (sauf si on a beaucoup de temps devant nous). Il y a la couverture qui permet de qualifier : est-ce qu’un test est utile ou pas ? Si mon code est bien couvert, alors mon test est efficace … hmm, il est au moins utile. Il pourra détecter plus de régressions, ou alors il rend caduque d’autres tests (intéressant).
    Qu’est-ce qu’une couverture ? Il en existe plusieurs : tous-les-nœuds, toutes-les-instructions, toutes-les-conditions, toutes-les-décisions, toutes-les-utilisations, …, tous-les-chemins. La dernière est la plus importante. Cette partie ne ressort pas bien dans ton article, tu parles de couverture et de comportement. Attention, ce n’est pas parce qu’on a une bonne couverture que le code fait ce qu’on veut ! Une bonne couverture va permettre de détecter des erreurs à l’exécution du code, mais en aucun cas elle ne validera le comportement du code. C’est la spécification (avec par exemple un test) qui sert à ça.

    Je terminerai en parlant du test automatique cette fois. Les tests expriment une spécification, mais pourquoi pas l’inverse ? On peut écrire une spécification (dans un langage formelle) et générer des tests à partir de ça. La spécification peut prendre plusieurs formes : par exemple celle de contrat. Dans ce cas, on parle de contract-based testing. On se sert de cette spécification pour générer des tests et des données de tests. Ah tiens intéressant, on arrive à ton problème concernant les données. Dans le cas du tests automatiques, les données sont générées automatiquement afin de générer (créer, maintenir, supprimer) des tests. L’objectif est d’assurer la meilleure couverture possible grâce à des données de test pertinentes. Cette fois-ci, on a toute la chaîne : la spécification définit le comportement de la fonction (grâce à un oracle qui nous dit si le résultat de l’exécution est bon ou pas), et on a la couverture pour savoir si le test généré (les données de tests) sont suffisamment pertinentes pour tester tous les cas/comportements (nous dire si le test est complet ou pas).

    Tu ne parles pas dans ton article des tests à partir de scénario, qui permettent d’exécuter des suites d’opérations (fonctions ou méthodes) automatiquement. Tu ne parles pas non plus du test paramétré qui sont des tests écrits manuellement mais paramétrés par des spécifications : ce sont des « meta »-tests. Et pourtant, ces tests répondent aux problèmes que tu évoques dans ton article.

    Voilà pour des petites précisions :-).

  • Ivan Enderlin
    janvier 21, 2013

    Bonjour,

    Je voulais apporter quelques précisions car il y a des amalgames.

    Il y a une matrice des tests. Sur un axe (appelons-le « type ») nous avons les tests unitaires, fonctionnels etc., et sur l’autre axe (appelons-le « mode d’exécution ») nous avons les tests manuels, automatisés, automatiques etc. Attention, il y a une différence entre automatisé et automatique, j’y reviens.

    Premier axe. Le test unitaire permet de valider atomiquement un logiciel (j’en profite pour préciser qu’il faut bien parler de « validation » et pas de « vérification », c’est très différent). Atomiquement ça veut dire qu’on valide les briques logiciels les plus petites, soit les fonctions ou méthodes. Ensuite, nous avons le test fonctionnel, qui peut aussi s’appeler le test d’intégration (c’est un abus de langage). L’objectif est bien de valider le fonctionnement de plusieurs briques entre elles, mais on reste au niveau du code. Il existe aussi le test fonctionnel à un niveau plus haut, quand on parle de fonctionnalités voulues par le client par exemple. Peu importe, le but est de tester une fonctionnalité, peu importe laquelle et le niveau. Bref, c’est pas mon propos.

    Second axe. Le test manuel, automatisé etc. Manuel ça veut dire qu’on va prendre notre souris et exécuter le test à la main, comme à l’époque : j’exécute ma fonction somme(2, 3) et je regarde que le résultat est 5, super, je coche vert dans mon tableau. Automatisé ça veut dire qu’on va faire exécuter les tests par une machine, à l’aide d’outils appelés frameworks de tests, comme atoum & consors. Ces outils permettent de dire : on va exécuter somme(2, 3), le résultat attendu est 5, et produit moi un rapport au passage.
    Le processus et la démarche sont strictement identiques ! Il n’y a aucune différence, sauf qu’on gagne beaucoup de temps et énormément d’argent car on peut les ré-exécuter autant que l’on le souhaite (comme tu l’as très bien souligné).

    Pour montrer les deux axes : le test unitaire peut être automatisé tout comme le test fonctionnel. Tu dis l’inverse dans ton article.

    Je vais un peu plus loin.

    Quel intérêt d’écrire un test ? Un test est une spécification. Il spécifie comment le code doit se comporter : je donne 2 et 3 à somme, il me calcule 5. C’est très léger comme spécification, car on ne dit pas comment la fonction somme se comporte avec 0 et -1 par exemple, mais c’est toujours ça ! La spécification est censée expliquer le comportement attendu d’une fonction : un bon comportement.

    Mais attention, les tests ne sont pas exhaustifs (sauf si on a beaucoup de temps devant nous). Il y a la couverture qui permet de qualifier : est-ce qu’un test est utile ou pas ? Si mon code est bien couvert, alors mon test est efficace … hmm, il est au moins utile. Il pourra détecter plus de régressions, ou alors il rend caduque d’autres tests (intéressant).
    Qu’est-ce qu’une couverture ? Il en existe plusieurs : tous-les-nœuds, toutes-les-instructions, toutes-les-conditions, toutes-les-décisions, toutes-les-utilisations, …, tous-les-chemins. La dernière est la plus importante. Cette partie ne ressort pas bien dans ton article, tu parles de couverture et de comportement. Attention, ce n’est pas parce qu’on a une bonne couverture que le code fait ce qu’on veut ! Une bonne couverture va permettre de détecter des erreurs à l’exécution du code, mais en aucun cas elle ne validera le comportement du code. C’est la spécification (avec par exemple un test) qui sert à ça.

    Je terminerai en parlant du test automatique cette fois. Les tests expriment une spécification, mais pourquoi pas l’inverse ? On peut écrire une spécification (dans un langage formelle) et générer des tests à partir de ça. La spécification peut prendre plusieurs formes : par exemple celle de contrat. Dans ce cas, on parle de contract-based testing. On se sert de cette spécification pour générer des tests et des données de tests. Ah tiens intéressant, on arrive à ton problème concernant les données. Dans le cas du tests automatiques, les données sont générées automatiquement afin de générer (créer, maintenir, supprimer) des tests. L’objectif est d’assurer la meilleure couverture possible grâce à des données de test pertinentes. Cette fois-ci, on a toute la chaîne : la spécification définit le comportement de la fonction (grâce à un oracle qui nous dit si le résultat de l’exécution est bon ou pas), et on a la couverture pour savoir si le test généré (les données de tests) sont suffisamment pertinentes pour tester tous les cas/comportements (nous dire si le test est complet ou pas).

    Tu ne parles pas dans ton article des tests à partir de scénario, qui permettent d’exécuter des suites d’opérations (fonctions ou méthodes) automatiquement. Tu ne parles pas non plus du test paramétré qui sont des tests écrits manuellement mais paramétrés par des spécifications : ce sont des « meta »-tests. Et pourtant, ces tests répondent aux problèmes que tu évoques dans ton article.

    Voilà pour des petites précisions :-).

    • Nicolas Hachet
      janvier 21, 2013

      Merci pour ce commentaire très complet.

      Effectivement, l’article ne couvre pas toutes les problématiques des tests. Ça pourra faire l’objet d’un prochain article.

  • Nicolas Hachet
    septembre 11, 2019

    Pour revenir au fond, résumons nos échanges :
    1. les TUA valident un fonctionnement
    2. les TUA engendrent un surcoût
    3. de ton point de vue, ce surcoût n’en est pas un puisque les TUA font parties des devs
    4. du mien, il est nécessaire d’évaluer la pertinence des TUA pour limiter l’impact du surcoût
    5. de ton point de vue, l’absence de TUA signifie faire du code pourri
    6. du mien, l’absence de TUA n’est pas un obstacle au fait de proposer des applications fonctionnelles, stables et répondant aux besoins

    En extrapolant un peu :
    7. tu serais partisan de remplacer les tests unitaires (voire fonctionnels) par des tests automatisés et de tout gérer programmatiquement
    8. je réponds que cela ne peut se faire qu’au détriment du budget et, dans le cas des tests fonctionnels, de la qualité des scénarios de tests.

    J’ai peut être oublié des choses mais je commence à fatiguer. 😉

  • Nicolas Hachet
    janvier 18, 2013

    Pour revenir au fond, résumons nos échanges :
    1. les TUA valident un fonctionnement
    2. les TUA engendrent un surcoût
    3. de ton point de vue, ce surcoût n’en est pas un puisque les TUA font parties des devs
    4. du mien, il est nécessaire d’évaluer la pertinence des TUA pour limiter l’impact du surcoût
    5. de ton point de vue, l’absence de TUA signifie faire du code pourri
    6. du mien, l’absence de TUA n’est pas un obstacle au fait de proposer des applications fonctionnelles, stables et répondant aux besoins

    En extrapolant un peu :
    7. tu serais partisan de remplacer les tests unitaires (voire fonctionnels) par des tests automatisés et de tout gérer programmatiquement
    8. je réponds que cela ne peut se faire qu’au détriment du budget et, dans le cas des tests fonctionnels, de la qualité des scénarios de tests.

    J’ai peut être oublié des choses mais je commence à fatiguer. 😉

  • mageekguy
    septembre 11, 2019

    Les tests manuals sont par nature faillible, au contraire des tests automatisés, et même si les tests unitaires ne remplacent pas les tests manuels, ils sont pour autant bien plus fiables et devraient être la règle plutôt que l’exception.
    Je préfère, et de très loin, avoir des tests automatisés que des tests manuels, car je n’ai absolument aucune assurance vis à vis de la qualité de ces derniers.
    Le développeur, la recette et même l’utilisateur finale ne pourront pas systématiquement tout tester sans faire d’omission ou d’erreur, au contraire des tests unitaires, exécutables de manière exhaustive et autant de fois que l’on veut par la machine.
    Et d’ailleurs, je ne sais pas ce qu’il peut y avoir comme test plus formel que celui exécuté par une machine.
    En clair, le test manuel n’est pas fiable et est coûteux puisqu’il faut bien financer le ou les humains qui les feront (et l’Humain est bien ce qui coûte le plus cher dans un développement, et de loin), là ou le test unitaire est fiable et bien moins coûteux puisque la machine à un coût bien moindre qu’une interface chaise/clavier.
    Quant à la phrase « de nombreux projets ne contiennent aucun TUA : ça ne les empêche pas de fonctionner à merveille et de répondre aux besoins », elle me fait doucement rigoler.
    Par expérience, je peux dire que dans les cas nominaux les plus répandus, lorsque tout va bien, ces projets font plus ou moins ce qu’ils doivent faire (encore ceux qui travaillent dans la TMA savent parfaitement qu’il n’en est rien, sinon, ils n’auraient aucune raison d’exister), mais lorsqu’un problème survient, ça ne sera certainement pas du tout la même chose.
    Quand à la maintenance des tests et des jeux de données, lorsque les choses sont faites correctement, ce n’est pas un problème.
    Le TDD permet en effet de la réaliser au fil de l’eau en fonction des besoins, et les mocks permettent de plus de s’affranchir de l’infrastructure technique.
    Enfin, le débat est effectivement aussi simple que de dire que les tests font partis du développement et de refuser de les supprimer pour alléger la facture.
    Si le client ne veut pas payer le prix demandé, soit il réduit son périmètre fonctionnel, soit il va chercher un prestataire moins cher.
    Je ne vois aucune raison pour qu’un outil technique indispensable à la bonne marche du produit final soit passé à la trappe pour une raison commerciale.
    Je me vois mal par exemple demander à un constructeur automobile, à un plombier, un électricien, un architecte ou tout autre artisan ou fabricant de supprimer ses contrôles qualités pour qu’il me vende moins cher sa prestation.
    Et pourtant, cela semble normal dans l’industrie du logiciel.
    Alors, de là à dire qu’il y a un problème, il n’y a qu’un pas.

  • jubianchi
    septembre 11, 2019

    Bonjour et merci pour cet article intéressant 🙂

    Je rejoins un peu @mageekguy sur le point concernant le test de code « simple » : je pense que justement il est très intéressant de tester un code jugé simple car, lorsqu’on va intervenir dessus (manuellement ou pas) on peut être tenté de se dire « c’est simple donc je ne ferais pas d’erreur » et c’est justement à ce moment là qu’on en fait.

    D’autre part, comme la signalé @mageekguy, une intervention non-manuelle (rechercher/remplacer, reformatage automatique, …) peut facilement introduire un bug si on ne fait pas assez attention.

    Il y a tout de même un point sur lequel je me suis arrêté dans l’article :
    > Imaginez testez toutes les structures conditionnelles imbriquées, toutes les fonctions, tous les enchaînements possibles…

    Lorsqu’on pratique le TDD, on arrive rarement dans un cas où, tester tous les chemins de code devient difficile car le test aura été écrit à priori et couvrira donc par avance l’unité testée (le code à écrire donc). Pour du test à posteriori, j’essaye d’obtenir dans un premier temps une couverture maximale, quitte à m’arracher un peu les cheveux et j’enchaîne directement par du refactoring pour améliorer le tout (dans la mesure du possible et du raisonnable).

    Le TDD est une méthode plus ou moins compliquée à adopter en fonction des personnes donc je veux bien admettre que dans certains cas, tester un morceau de code complexe (à posteriori donc) peut être difficle.

    Pour le reste, je suis assez d’accord : les tests unitaires et le TDD, c’est bien, ça aide beaucoup à travailler chaque jour en nous obligeant à rester dans un cadre défini. Les tests automatisés, c’est encore mieux car on est sur que le code répond toujours de la même manière.

    Après, comme dit dans l’article, les tests unitaires ne font pas tout. C’est pour cela que l’ajout d’une suite de test d’intégration et de tests fonctionnels est également très utile.

  • mageekguy
    septembre 11, 2019

    Très bonne présentation des tests unitaires en général et plus particulièrement de leurs intérêts et de leurs limites.
    Cependant, j’ai deux remarques.
    Tout d’abord, je ne peut m’empêcher de hurler lorsqu’on dit que les tests introduisent un surcoût.
    En effet, ils ne peuvent pas un surcoût car tester, c’est développer et de plus, c’est développer correctement.
    Ce n’est pas une tâche à part, une chose optionnelle, une travail supplémentaire ou que sais-je encore : écrire et exécuter des tests est une tâche faisait partie intégrante de tout développement, et ne pas le faire est juste mal faire le travail.
    En conséquence, le coût des tests est (ou du moins devrait) être intégré au coût global du développement et cette notion de « surcoût » n’a pas lieu d’être.
    Ensuite, je ne suis pas d’accord avec ton exemple mathématique censé illustrer le fait qu’il n’est pas utile de tout tester.
    J’ai écris je ne sais combien de ligne de code, et j’ai écris je ne sais combien de ligne de test depuis maintenant presque 8 ans que je pratique le TDD.
    Et la grande majorité des bugs que je corrige dans mon code proviennent toujours de méthodes qui n’ont pas été testées car jugées très ou trop simple dans un premier temps.
    Sauf que, par exemple, je ne suis pas à l’abri d’une erreur de frappe, d’un chercher/remplacer insuffisament restrictif.
    Alors certes, sur le moment, un tel test peut paraître stupide, mais sur le long terme, c’est une ceinture de sécurité qui me permet d’être quasi certain que la méthode fait ce que je veux, que je n’y ai pas introduit de régression suite à une modification non manuelle du code, ou, plus vicieux, que quelqu’un d’autre dans le cadre d’un travail en équipe n’a pas modifié le comportement de la méthode et a du même coup introduit par effet de bord un bug dans une autre méthode.

  • mageekguy
    janvier 18, 2013

    Les tests manuals sont par nature faillible, au contraire des tests automatisés, et même si les tests unitaires ne remplacent pas les tests manuels, ils sont pour autant bien plus fiables et devraient être la règle plutôt que l’exception.
    Je préfère, et de très loin, avoir des tests automatisés que des tests manuels, car je n’ai absolument aucune assurance vis à vis de la qualité de ces derniers.
    Le développeur, la recette et même l’utilisateur finale ne pourront pas systématiquement tout tester sans faire d’omission ou d’erreur, au contraire des tests unitaires, exécutables de manière exhaustive et autant de fois que l’on veut par la machine.
    Et d’ailleurs, je ne sais pas ce qu’il peut y avoir comme test plus formel que celui exécuté par une machine.
    En clair, le test manuel n’est pas fiable et est coûteux puisqu’il faut bien financer le ou les humains qui les feront (et l’Humain est bien ce qui coûte le plus cher dans un développement, et de loin), là ou le test unitaire est fiable et bien moins coûteux puisque la machine à un coût bien moindre qu’une interface chaise/clavier.
    Quant à la phrase « de nombreux projets ne contiennent aucun TUA : ça ne les empêche pas de fonctionner à merveille et de répondre aux besoins », elle me fait doucement rigoler.
    Par expérience, je peux dire que dans les cas nominaux les plus répandus, lorsque tout va bien, ces projets font plus ou moins ce qu’ils doivent faire (encore ceux qui travaillent dans la TMA savent parfaitement qu’il n’en est rien, sinon, ils n’auraient aucune raison d’exister), mais lorsqu’un problème survient, ça ne sera certainement pas du tout la même chose.
    Quand à la maintenance des tests et des jeux de données, lorsque les choses sont faites correctement, ce n’est pas un problème.
    Le TDD permet en effet de la réaliser au fil de l’eau en fonction des besoins, et les mocks permettent de plus de s’affranchir de l’infrastructure technique.
    Enfin, le débat est effectivement aussi simple que de dire que les tests font partis du développement et de refuser de les supprimer pour alléger la facture.
    Si le client ne veut pas payer le prix demandé, soit il réduit son périmètre fonctionnel, soit il va chercher un prestataire moins cher.
    Je ne vois aucune raison pour qu’un outil technique indispensable à la bonne marche du produit final soit passé à la trappe pour une raison commerciale.
    Je me vois mal par exemple demander à un constructeur automobile, à un plombier, un électricien, un architecte ou tout autre artisan ou fabricant de supprimer ses contrôles qualités pour qu’il me vende moins cher sa prestation.
    Et pourtant, cela semble normal dans l’industrie du logiciel.
    Alors, de là à dire qu’il y a un problème, il n’y a qu’un pas.

    • Nicolas Hachet
      janvier 18, 2013

      Je préfère, et de très loin, avoir des tests automatisés que des tests manuels, car je n’ai absolument aucune assurance vis à vis de la qualité de ces derniers.

      Ça fera plaisir aux testeurs, recetteurs et autres personnes dont c’est le métier (petite boutade, je vois bien où tu veux en venir ;)).

      Bien sûr les tests manuels sont faillibles et perfectibles. En revanche, ils restent absolument nécessaires pour valider le fonctionnel d’une application (nous pourrions parler des tests fonctionnels automatisés mais c’est un autre débat). Ton raisonnement laisse penser que les tests automatisés remplacent les tests manuels. En réalité, les TUA viennent en complément des tests manuels. Ils permettent éventuellement de se passer de la phase de recette technique mais ils sont loin d’égaler des tests fonctionnels.

      Pour l’heure, les TUA, tout comme le TDD, ne sont pas la norme, même si tu oeuvres pour que les choses bougent. Je ne dis pas que telle ou telle chose est mieux qu’une autre, que les TUA sont inutiles car trop cher. Le fait est que leur maintenance et la gestion du jeu de données (me) font perdre un temps considérable. D’ailleurs si tu as des solutions pour gérer efficacement le jeu de données, je suis preneur : ça peut être l’objet d’un article dédié.

      Quant au fait que les tests soient une composante inséparable du produit final, c’est l’éternel débat entre technique et commerce. Qu’on aime son travail est une chose. Qu’on perdre des affaires parce qu’on souhaite faire les choses à notre manière est une autre. Tu sais aussi bien que moi que ce sont les clients qui choisissent les prestataires et que, souvent, un prestataire trop cher est un prestataire au chômage. De même, entre une application qui fonctionne et une application qui fonctionne pour 20% moins cher, le client n’hésitera pas longtemps.

      En tout cas merci pour tes interventions, je trouve ce débat intéressant (même si j’ai bien compris que, pour toi, débat il n’y avait pas ! ;)).

      • mageekguy
        janvier 18, 2013

        Il n’y a aucune attaque personnelle de ma part.
        Ton CV est en ligne et publiquement consultable, et mon avis quand à ton expérience en découle directement, ainsi que de tes écrits, tout aussi publique.
        De plus, si je n’accordais aucun crédit à tes écrits, je n’aurais même pas posté mon premier commentaires, que tu devrais d’ailleurs prendre la peine de relire.
        Je peux comprendre que je bouscule ta conception de ton métier, d’autant que c’est l’objectif, mais n’y voit absolument rien de personnel (et c’est d’ailleurs mal me connaître que de penser cela).
        J’ai beaucoup aimé ton « Ça fera plaisir aux testeurs, recetteurs et autres personnes dont c’est le métier » étant donné que ma femme est responsable de tests fonctionnels ;).
        Et si les tests manuels restent encore indispensables pour valider le fonctionnel d’une application (encore que j’en demande la preuve dans le cadre d’un environnement de test automatisé fulle stack), ils devraient être en bout de chaîne en complément et non la garantie principale.
        Quand à comparer tests unitaires et tests fonctionnels, c’est totalement stupides, étant donné qu’ils n’ont pas du tout les mêmes objectifs et sont pour autant complémentaire, l’un comblant les faiblesses de l’autre et réciproquement (tout comme les tests manuels en bout de chaîne permette de localiser les bugs qui sont passés au travers des tests automatisés).
        J’ajoute que ce n’est pas parce qu’une chose n’est pas la norme qu’elle ne doit pas être faite, surtout lorsqu’il s’agit d’une bonne pratique (en général, je suis plus cru et je dis que ce n’est pas parce que tout le monde fait de la merde qu’il faut en faire autant et poursuivre dans la même voie…).
        Quand à la gestion du jeux de données, je t’invite à lire tout ce que tu pourras trouver concernant les mocks, ça te donnera quelques pistes.
        Et enfin, si tout les prestataires faisaient leur travail correctement et n’acceptait pas de passer à la trappe une composante essentielle à la bonne réalisation de leur travail, le client n’aurait plus le même choix, d’autant qu’il signe le devis sans savoir ce qu’il obtiendra réellement au final.
        Mais pour le coup, c’est réellement un autre débat, beaucoup plus vaste.

        • Nicolas Hachet
          janvier 18, 2013

          Disons que si mon CV se résumait à ce qui est en ligne, je ne me permettrais même pas d’écrire publiquement. 😉

          Maintenant, mon métier bouge tous les jours. Comme tous les informaticiens, j’apprends tous les jours. C’est ce qui fait que c’est passionnant. Je n’ai pas de certitude établie, même si certains écrits ont pu te le laisser penser.

          Si nous avions ce débat d’ici quelques temps, mes positions auront assurément bougé, dans un sens ou dans l’autre. Le fait est que ton point de vue est sans concession. A l’heure actuelle, le mien tend à nuancer ce que tu considères comme « faire de la merde » car ne pas tester automatiquement ne signifie pas ne pas tester du tout.

    • Nicolas Hachet
      janvier 18, 2013

      Je ne vois pas ce que mon âge vient faire la dedans. Tu n’as aucune idée de mes expériences.
      Que tu n’accordes aucun crédit à mes écrits est une chose, que tu prennes le temps de venir l’écrire en est une autre.

      Pour clore ce qui va vite devenir stérile, je t’invite à écrire un article en guise réponse. Ça permettra de stopper des attaques personnelles qui n’ont pas lieu d’être.

  • jubianchi
    janvier 18, 2013

    Bonjour et merci pour cet article intéressant 🙂

    Je rejoins un peu @mageekguy sur le point concernant le test de code « simple » : je pense que justement il est très intéressant de tester un code jugé simple car, lorsqu’on va intervenir dessus (manuellement ou pas) on peut être tenté de se dire « c’est simple donc je ne ferais pas d’erreur » et c’est justement à ce moment là qu’on en fait.

    D’autre part, comme la signalé @mageekguy, une intervention non-manuelle (rechercher/remplacer, reformatage automatique, …) peut facilement introduire un bug si on ne fait pas assez attention.

    Il y a tout de même un point sur lequel je me suis arrêté dans l’article :
    > Imaginez testez toutes les structures conditionnelles imbriquées, toutes les fonctions, tous les enchaînements possibles…

    Lorsqu’on pratique le TDD, on arrive rarement dans un cas où, tester tous les chemins de code devient difficile car le test aura été écrit à priori et couvrira donc par avance l’unité testée (le code à écrire donc). Pour du test à posteriori, j’essaye d’obtenir dans un premier temps une couverture maximale, quitte à m’arracher un peu les cheveux et j’enchaîne directement par du refactoring pour améliorer le tout (dans la mesure du possible et du raisonnable).

    Le TDD est une méthode plus ou moins compliquée à adopter en fonction des personnes donc je veux bien admettre que dans certains cas, tester un morceau de code complexe (à posteriori donc) peut être difficle.

    Pour le reste, je suis assez d’accord : les tests unitaires et le TDD, c’est bien, ça aide beaucoup à travailler chaque jour en nous obligeant à rester dans un cadre défini. Les tests automatisés, c’est encore mieux car on est sur que le code répond toujours de la même manière.

    Après, comme dit dans l’article, les tests unitaires ne font pas tout. C’est pour cela que l’ajout d’une suite de test d’intégration et de tests fonctionnels est également très utile.

    • Nicolas Hachet
      janvier 18, 2013

      Merci pour ce commentaire.

      Je reviens sur ce point :

      « Imaginez testez toutes les structures conditionnelles imbriquées, toutes les fonctions, tous les enchaînements possibles… » Lorsqu’on pratique le TDD, on arrive rarement dans un cas où, tester tous les chemins de code devient difficile car le test aura été écrit à priori et couvrira donc par avance l’unité testée

      Effectivement en théorie, les tests sont écrits par avance. Cela implique néanmoins une phase de conception technique poussée qui, malheureusement, est rarement suffisante pour arriver à un niveau de détail permettant d’écrire l’ensemble des tests.

      En ce sens, je considère qu’une couverture à 100% de TUA est du domaine du rêve, sauf pour les librairies et composants réutilisables.

      • mageekguy
        janvier 18, 2013

        Lorsque je lis « Cela implique néanmoins une phase de conception technique poussée », je me dis que tu n’as strictement rien compris au TDD ou que tu n’en a jamais fait.
        En effet, tout son intérêt repose justement sur le fait qu’il permet de faire émerger l’architecture des tests en confrontant le développeur a une problématique réelle avant même d’avoir écris la moindre ligne de code.
        Le TDD oblige a se poser des questions et permet d’améliorer sa conception tout en ayant l’assurance que le code continue à se comporter comme il le doit.
        De plus, pour être testable, un code doit être écrit en suivant les bonnes pratiques.
        Donc, l’un dans l’autre, il n’y a aucunement besoin de réaliser « une phase de conception technique poussée », bien au contraire : les tests sont la phase de conception et cerise sur le gâteau, ils permettent de valider le fait que les spécifications sont toujours valides même après des évolutions ou des modifications automatiquement et rapidement !

        • Nicolas Hachet
          janvier 18, 2013

          Les tests ne s’écrivent pas naturellement et une « phase de conception technique poussée » est indispensable. Dans le cadre d’un développement en mode projet, en équipe, il est nécessaire de structurer l’application, de l’architecturer et de faire émerger les tests de cette phase de conception. Reste ensuite à remplir joyeusement le code qui fera passer les tests au vert.

          Je n’adhère à l’adage : le code est la documentation. Le code + les commentaires + les tests sont une partie de la documentation seulement. En réduisant la documentation aux tests, on limite sa portée aux techniciens.

          En revanche, j’adhère totalement au principe d’une pré-validation du code par les TUA. Qu’on soit en TDD ou pas, c’est le meilleur moyen de pratiquer les tests unitaires automatisés.

          • mageekguy
            janvier 18, 2013

            Et c’est à moi que tu dis ça…
            Excuse moi, mais je me marre, merci pour cette instant de franche rigolade :D, car je fais ce que je t’ai expliqué au quotidien, aussi bien dans mes projets personnels que professionnels.
            Contrairement à toi qui vient à peine de sortir de l’œuf, j’ai un peu d’expérience dans le domaine du test, et je peux donc te dire que tu te trompes totalement et je ne saurais trop te conseiller de ne pas avoir trop de certitudes, même si je sais pertinemment que tu ne tiendra aucun compte de cette recommandation.
            Car même si je n’avais pas cette expérience, des études et des recherches effectuées par des gens bien plus compétents que toi ou moi ont démontré que tu te trompes.
            Expérimente, mets en pratique ce dont tu parles, et après tu pourras en discuter sérieusement.
            Là, tu te contente de plaquer un concept qui te sembles intéressant sur ce que tu as vécu en SSII.
            Et je te le dis très gentiment : la SSII est le pire endroit au monde pour apprendre à concevoir un logiciel correctement.

          • Nicolas Hachet
            janvier 18, 2013

            Je ne vois pas ce que mon âge vient faire la dedans. Tu n’as aucune idée de mes expériences.
            Que tu n’accordes aucun crédit à mes écrits est une chose, que tu prennes le temps de venir l’écrire en est une autre.

            Pour clore ce qui va vite devenir stérile, je t’invite à écrire un article en guise réponse. Ça permettra de stopper des attaques personnelles qui n’ont pas lieu d’être.

      • mageekguy
        janvier 18, 2013

        Accessoirement, en PHP, le taux de couverture des tests ne veut strictement rien dire et devrait être considéré tout au plus comme un indicateur, et non comme une garantie du fait que les tests sont exhaustifs.
        De plus, il y a différent type de couverture.
        Enfin, un autre avantage du TDD est qu’il rend inutile cette notion de couverture, chaque instruction écrite par le développeur devant obligatoirement et uniquement permettre aux tests de passer du rouge au vert.
        En conséquence, chaque instruction du code est donc forcément exécutée lors des tests.

  • mageekguy
    janvier 18, 2013

    Très bonne présentation des tests unitaires en général et plus particulièrement de leurs intérêts et de leurs limites.
    Cependant, j’ai deux remarques.
    Tout d’abord, je ne peut m’empêcher de hurler lorsqu’on dit que les tests introduisent un surcoût.
    En effet, ils ne peuvent pas un surcoût car tester, c’est développer et de plus, c’est développer correctement.
    Ce n’est pas une tâche à part, une chose optionnelle, une travail supplémentaire ou que sais-je encore : écrire et exécuter des tests est une tâche faisait partie intégrante de tout développement, et ne pas le faire est juste mal faire le travail.
    En conséquence, le coût des tests est (ou du moins devrait) être intégré au coût global du développement et cette notion de « surcoût » n’a pas lieu d’être.
    Ensuite, je ne suis pas d’accord avec ton exemple mathématique censé illustrer le fait qu’il n’est pas utile de tout tester.
    J’ai écris je ne sais combien de ligne de code, et j’ai écris je ne sais combien de ligne de test depuis maintenant presque 8 ans que je pratique le TDD.
    Et la grande majorité des bugs que je corrige dans mon code proviennent toujours de méthodes qui n’ont pas été testées car jugées très ou trop simple dans un premier temps.
    Sauf que, par exemple, je ne suis pas à l’abri d’une erreur de frappe, d’un chercher/remplacer insuffisament restrictif.
    Alors certes, sur le moment, un tel test peut paraître stupide, mais sur le long terme, c’est une ceinture de sécurité qui me permet d’être quasi certain que la méthode fait ce que je veux, que je n’y ai pas introduit de régression suite à une modification non manuelle du code, ou, plus vicieux, que quelqu’un d’autre dans le cadre d’un travail en équipe n’a pas modifié le comportement de la méthode et a du même coup introduit par effet de bord un bug dans une autre méthode.

    • Nicolas Hachet
      janvier 18, 2013

      Je savais que la question du surcoût ferait crier dans les chaumières. 🙂

      J’ai 5 éléments de réponse :

      • 1. tester est une composante du développement, je suis entièrement d’accord. Ne pas tester, c’est mal faire son travail. Dans le cadre d’un développement, qu’il soit industrialisé ou non, les tests sont, loin d’être un surcoût, une obligation stricte.
      • 2. tester unitairement de manière automatisée est une bonne pratique mais ça ne remplace pas les tests manuels. On peut tout à fait les intégrer de manière transparente dans les charges de développements mais il y aura forcément des phases de tests plus formelles.
      • 3. dans le cadre de développements réalisés en mode projet, les développements stricto sensu interviennent entre les spécifications et les deux phases de recettes techniques et fonctionnelles. Ainsi, il y a d’autres garde-fous permettant d’assurer une application stable et d’éviter les bugs.
      • 4. de nombreux projets ne contiennent aucun TUA : ça ne les empêche pas de fonctionner à merveille et de répondre aux besoins. C’est ce qui me fait dire que les TUA sont parfois galvaudés. Un projet ne contenant aucun TUA ne sera pas forcément moins bon qu’un projet couvert à 100%. C’est en ce sens que les TUA constituent un surcoût.
      • 5. la question du jeu de données et de sa maintenance est le coeur du problème des TUA. Les évolutions de spécifications en cours de développement,- qui sont une réalité que l’on ne peut ignorer -, font que la maintenance de tout ce petit monde est coûteuse et entraîne des pertes de charges importantes.

      Pour le reste, oui les tests permettent d’éviter des erreurs d’inattention. Mais un test manuel détectera tout aussi bien le même bug. L’avantage réside dans la simplicité de rejouer les tests unitaires et c’est bien ici que les TUA peuvent devenir rentables…voire indispensables dans le cadre de développements sensibles.

      Maintenant, j’aimerais que le débat soit aussi simple que « les TUA font parties des devs ». La réalité fait qu’ils sont généralement perçus comme une charge et que les clients n’ayant pas de vision technique n’en voient pas l’intérêt et regardent essentiellement la facture.

Leave a Reply

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *