Article publié dans Linux Magazine 89, décembre 2006.
Copyright © 2006 - Sébastien Aperghis-Tramoni, Philippe Blayo
Le précédent article vous a décrit les bases du mécanisme général de
test en Perl, le protocole TAP et le module associé Test::Harness
,
ainsi que le module de test le plus courant et le plus utile pour
débuter, Test::More
. Nous allons maintenant voir comment bien gérer
ses tests, et étudier l'utilisation de quelques-uns des nombreux modules
disponibles sur le CPAN pour simplifier la mise en œuvre de
sémantiques complexes.
Voyons d'abord quelques bonnes pratiques de gestion des scripts de tests.
Premier point important à garder à l'esprit, le code des scripts de tests est du code Perl comme les autres. Il n'y a donc pas de raison pour qu'il soit plus mal écrit qu'un autre. En fait, comme c'est typiquement du code que la plupart des personnes n'aiment pas écrire ni relire, il est préférable, encore plus que pour le reste du code, qu'il soit écrit de manière propre, lisible et maintenable. Qu'est-ce que cela signifie ? Que le code devrait dans la mesure du possible être écrit en respectant les règles de bonnes pratiques que vous avez l'habitude d'utiliser, par exemple que le code doit passer avec les strictures activées. Autre conseil, éviter de réinventer les roues qui existent déjà. Cela se traduit par l'utilisation de modules qui fournissent ces roues, modules qui seront justement présentés dans les articles qui suivront celui-ci.
Une question qui peut venir à l'esprit est de savoir comment répartir
ses tests. Vaut-il mieux les mettre tous dans un seul script ou au
contraire les répartir le plus possible ? La réponse est que cela
dépend :-)
En particulier, cela dépend du code testé et du nombre de fonctions
ou fonctionnalités à tester. S'il ne possède qu'un nombre restreint
de fonctions ou un périmètre fonctionnel très délimité, l'ensemble
des tests peuvent alors tenir dans un seul script court. Par exemple,
le module XSLoader
, qui fait partie de la distribution standard de
Perl, contient l'ensemble de ses tests fonctionnels dans le script
t/XSLoader.t
. La raison en est que ce module, dont le rôle est de
servir d'interface simplifiée à DynaLoader
, le chargeur dynamique
de bibliothèques externes, ne fournit qu'une seule fonction, load()
.
Comme celle-ci ne s'utilise pas de dix manières différentes, on a vite
fait de tout tester !
Quand le code à tester comporte un périmètre fonctionnel plus important, la solution la plus pratique est d'écrire un script par fonction, groupe de fonctions, fonctionnalité ou type de données à traiter, suivant les spécificités du code en question. C'est généralement la méthode retenue par beaucoup de développeurs publiant leurs modules sur le CPAN. Regardons quelques exemples plus en détails.
Dans le cas du module Parse::Syslog::Mail
, assez simple puisqu'il
ne fournit que deux méthodes, les scripts se répartissent les tâches
de la manière suivante :
01api.t se contente de vérifier si les méthodes de ce module fonctionnent comme prévu;
03diag.t vérifie les messages d'erreur et d'avertissement que le module peut produire;
10fields.t et 11values.t vérifient que le module analyse correctement des ensembles de données (fournis avec le module); le premier vérifie mécaniquement que les champs ont bien été isolés, le second que les valeurs renvoyées correspondent à ce qui était attendu.
Dans le cas d'un module plus complexe comme Net::Proxy
, la suite
de tests doit aussi réaliser des tests plus élaborés.
les premiers scripts, de 10proxy_new.t à 14proxy_debug.t se contentent de tester l'API du module, en invoquant le constructeur de différentes manières;
les scripts suivants, en particulier les 30tcp_tcp.t à 33dual.t, sont plus sophistiqués et créent des clients et serveurs réseaux afin de tester les fonctionnalités de proxying proprement dite du module.
Enfin, pour les modules poids lourds comme LWP
ou POE
, leur suite
de tests est suffisamment importante pour que les scripts soient rangés
dans des sous-répertoires distincts dans le répertoire t/, nommés en
fonction du domaine de fonctionnalités testées. Par exemple, dans le cas
de POE
, les scripts de test sont classés de manière très hiérarchisée :
le répertoire 10_units/ contient une large suite de tests unitaires permettant de vérifier les fonctionnalités de base, dont les tubes, les filtres et les exceptions;
20_resources/ contient
Un point qui peut sembler très contraignant à beaucoup de développeurs
(il l'est pour l'un des auteurs) est de devoir compter les tests d'un
script. Après tout, il est bien plus facile d'utiliser l'option no_plan
et de laisser les modules faire le travail de comptage des points de
test à notre place. De prime abord, ce point de vue peut sembler
raisonnable puisqu'après tout, les programmes sont a priori là pour
nous libérer des tâches fastidieuses, et compter le nombre de tests
exécutés en fait partie. Et c'est en effet le cas, mais seulement
pour les tests exécutés : à tout moment, on peut savoir combien
de tests ont été exécutés (que ce soit avec succès ou échec). Mais le
script de test n'a aucun moyen de savoir combien de tests restent
à exécuter. Cette information ne peut lui être fournit que de
l'extérieur, par la personne qui écrit le script.
Test::More
À noter que lorsqu'on parle des fonctionnalités de Test::More
,
celles-ci sont généralement héritées de Test::Builder
qui est le
module d'architecture sur lequel il s'appuie, et qu'elles s'appliquent
donc en règle générale aux modules qui dérivent eux aussi de Test::Builder
.
Le programmeur paresseux va se demander quelle est l'importance de
connaître à l'avance le nombre de tests à exécuter. La réponse est
qu'il s'agit d'une contrainte de cohérence du script, permettant de
vérifier son bon déroulement. La raison technique se trouve dans la
description du protocole TAP de l'article précédent. Dans le cas normal,
quand le plan est déclaré en début de session, l'interpréteur TAP
(dans le cas général, il s'agit de Test::Harness
) sait combien
de tests doivent être exécutés par le script. Si celui-ci meurt en
cours d'exécution, l'interpréteur TAP est donc en mesure de s'en rendre
compte car il verra une interruption du flux avant le nombre prévu :
1..51 ok 1 ok 2 ... ok 22 ok 23
Ici, le script s'arrête après le point 23 alors qu'il aurait dû en
exécuter 51. De plus, Test::Harness
va recueillir le statut de
sortie du script et le décoder pour essayer d'en savoir plus.
Test::More
par exemple, sort avec comme valeur de statut le
nombre de tests qui ont échoué.
On peut trouver là une nouvelle raison de penser qu'il n'y a pas besoin de
s'ennuyer avec le comptage des tests puisque en cas d'erreur, Test::More
va positionner le statut avec une valeur d'erreur qui sera détectée par
Test::Harness
. Sauf qu'il y a plusieurs problèmes qui font que ce
n'est pas vrai partout, et que cela va de moins en moins le devenir à
l'avenir. Car si Test::Harness
a actuellement une forte
dépendance par rapport au statut de sortie du script de test, cela provient
surtout du l'histoire de Perl, qui est un langage né sur Unix. Or Perl
est disponible sur un grand nombre de plates-formes, et certains systèmes
n'ont pas la même notion de statut de sortie, voire pas de statut du tout.
Ce mécanisme fonctionne bien sous Unix, mais il n'est pas garanti ailleurs.
Portabilité
Après tout, cela ne devrait surprendre personne, Perl étant un langage
contenant un nombre non négligeable de fonctions n'ayant vraiment de sens
que sous Unix, et dont certaines sont très difficiles, voire impossibles
à émuler sur des systèmes ne possédant pas de fonctions équivalentes,
comme par exemple fork()
, chroot()
, chmod()
, chown()
,
kill()
, link()
, les fonctions IPC System V, et d'autres encore.
De plus, deux autres phénomènes tendent à pousser à l'abandon à plus ou
moins court terme de l'utilisation du statut comme déterminant primaire
du succès ou de l'échec d'un script. Le premier est l'utilisation de
Test::Harness
pour tester des programmes écrits dans d'autres langages
de programmation. Suivant les langages considérés, le positionnement
du statut de sortie peut ou non être supporté, et, même quand il l'est,
ne pas être positionné pour des raisons culturelles (on peut notamment
penser à Java).
Le second est que le flux TAP peut ne pas être généré par un script
directement contrôlé par Test::Harness
mais provenir d'une source
extérieure, par exemple au travers du réseau en utilisant HTTP comme
mécanisme de transfert. Il n'y aura alors pas de notion de statut du
tout.
Enfin et plus fondamentalement, le statut de retour du script ne fait
pas partie de la spécification du protocole TAP. Il s'agit d'une
propriété externe au protocole, un artefact qui a été conservé parce
qu'il avait son utilité, même s'il avait aussi ses inconvénients.
On peut noter d'ailleurs que le positionnement du statut est de plus
une spécificité de Test::More
, les scripts produisant leur sortie
ok / not ok
à la main ne le positionnant typiquement pas (sauf
quand ils meurent violemment), pas plus que certains scripts basés
sur Test
, qui peuvent fournir un statut de succès malgré l'échec
de certains points de tests.
En résumé, cette contrainte, si elle en est une pour le programmeur, permet aussi de mieux limiter le périmètre d'exécution du script et donc de mieux diagnostiquer les problèmes. Pour compenser, nous allons vous montrer quelques techniques pour aider le comptage des tests.
Un premier moyen simple est d'utiliser no_plan
...
Oui, on vient de vous dire qu'il ne fallait pas l'utiliser, mais
calmez-vous et écoutez plutôt la suite de l'explication :-)
Un premier moyen simple, donc, est d'utiliser no_plan
pendant
la phase d'écriture du script, puis, une fois que celui-ci est
en grande partie écrit, il suffit de l'exécuter de manière verbeuse
avec prove -bv t/script.t
par exemple, et de relever le nombre
total de tests exécutés. Il suffit alors de positionner votre plan
à cette valeur. Par la suite, il suffira de l'incrémenter lors de
l'ajout d'autres points de tests.
Pour les scripts de tests s'appuyant sur des données dont la quantité
peut varier ou être paramétrée, une bonne technique consiste justement
à déterminer le nombre de tests par donnée analysée. Il suffit ensuite
de calculer le nombre total de tests par une simple multiplication (et
éventuellement l'addition des tests non dépendants des données). Ainsi
la plupart des scripts de test du module Net::Pcap
commencent de la
manière suivante :
my $total = 10; # number of packets to process plan tests => $total * 19 + 5;
Plusieurs des scripts sont en effet orientés autour de la capture de
paquets réseau (ce qui n'est pas une surprise puisque c'est le rôle de
Net::Pcap
), sur lesquels sont effectués plusieurs tests de cohérence,
dans cet exemple 19 par paquet. Si les tests portent sur des données
spécifiques, il suffit de le stocker dans un endroit facilement accessible
et de compter la quantité de données avant la déclaration du plan.
« L'endroit » peut être par exemple une chaîne, un tableau, un hash
ou dans la partie DATA
du script. Une bonne illustration de cette
méthode est donnée dans les scripts du module Pod::POM::View::HTML::Filter
:
my @tests = map { [ split /^---.*?^/ms ] } split /^===.*?^/ms, << 'TESTS'; =begin filter foo bar foo bar baz =end --- <html><body bgcolor="#ffffff"> <p>bar bar bar baz</p> </body></html> === ... === =begin filter verb verbatim block verbatim textblock =end --- <html><body bgcolor="#ffffff"> <pre> verbatim block</pre> <pre>verbatim textblock</pre> </body></html> TESTS plan tests => scalar @tests + 2;
Ici, les données sont stockées sous forme d'enregistrements séparés
par des ===
, chaque enregistrement étant lui-même constitué de deux
parties séparées par des ---
. La première partie correspond à des
fragments de documents Pod, la seconde au résultat attendu de la
conversion du fragment par le module testé, Pod::POM::View::HTML::Filter
[1].
Perl Best Practices
Comme le titre l'indique, Damian Conway a rassemblé dans ce livre 256 règles de bonnes pratiques d'écriture de code Perl, basées sur sa propre expérience ainsi que de celle d'autres grands programmeurs Perl. Ces règles permettent, si on les suit, d'écrire un code qui sera lisible pour un maximum de personnes, facilitant ainsi d'autant sa maintenabilité. À peu près tous les sujets qu'un programmeur Perl pourrait rencontrer son traités, des boucles et structures de contrôles aux hiérarchies de classes et d'objets, en passant par la gestion des entrées-sorties, des erreurs, des options en ligne de commande, ainsi que les meilleures manières d'écrire et d'utiliser les fonctions, les expressions régulière et la documentation.
Il propose aussi d'un chapitre consacré aux tests, conseillant de ne
pas hésiter à utiliser Test::More
et consorts dès que possible, par
exemple pour transformer en script de test tout bug applicatif rapporté.
Écrivant avec son humour habituel, Damian explique en détail le pourquoi de chaque règle. Ainsi, même si on n'est pas de prime abord d'accord avec ses choix, il est très intéressant de lire ses explications, qui décrivent en quoi cela peut sinon nuire à la lisibilité et à la compréhension du code.
On ne peut résister à l'envie de vous citer la première et probablement l'une des plus importante règles données par Damian :
Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
Disponible en français sous le titre De l'art de programmer en Perl.
Un autre exemple de cette technique est donné dans le script de test
principal du module File::Read
:
my @one_result_tests = ( ... { args => [ File::Spec->catfile(qw(t samples pi)) ], expected => "3.14159265358979\n", }, { args => [ File::Spec->catfile(qw(t samples hello)) ], expected => 'Hello', }, { args => [ File::Spec->catfile(qw(t samples world)) ], expected => "world\n", }, ... );
Le concept est exactement le même, sauf que les données sources et les résultats attendus sont stockés dans une structure Perl, réduisant ainsi le comptage des tests à quelques multiplications et additions triviales :
plan tests => 5 + @one_result_tests * 2 + ...
Enfin, une dernière technique, utilisée par Jérôme Quelin, permet de
simplifier encore le comptage. Elle consiste à utiliser des blocs
BEGIN
pour effectuer le compte, bloc par bloc, au fur et à mesure
de la compilation du script :
use strict; use Test::More; my $tests; # nombre total de tests plan tests => $tests; # déclaration du plan BEGIN { $tests += 4 } # tests du chargement et de l'API use_ok('WebBrowser'); can_ok('WebBrowser', qw(new clone load save)); my $browser = WebBrowser->new isa_ok($browser, 'WebBrowser'); can_ok($browser, qw(clone load save)); BEGIN { $tests += 5 } # tests de la méthode load() my $test_page = 'test.html'; eval { $browser->load($test_page) }; is( $@, '', "méthode load(), argument : $test_page" ); ok( $browser->status_ok, "page correctement chargée" ); is( $browser->current_page->location, $test_page ); is( $browser->current_page->size, -s $test_page ); like( $browser->current_page->title, '/Page de test/' );
L'astuce est que les blocs BEGIN
sont exécutés dès la phase de
compilation du script. Comme la variable $tests
a tout de même
été déclarée au début du script (3e ligne), elle est déjà allouée
et connue. Les blocs BEGIN
incrémentent ensuite sa valeur au fur
et à mesure de la compilation du script. Lorsque celui-ci est
finalement exécuté, la variable $tests
contient donc le nombre
total de tests, qui est donné comme plan.
Écriture par paragraphe
Recommandée par Damian Conway dans Perl Best Pratices[3], ce mode d'écriture consiste à séparer le code en petits ensembles de lignes, plus faciles à digérer qu'un gros bloc.
Cette technique est particulièrement bien adaptée pour les scripts de tests nécessitant d'être un peu long. Elle se marie de plus à merveille avec l'écriture par paragraphe et permet ainsi de simplifier d'autant la maintenance du programme : le compte des tests effectués se faisant localement à chaque paragraphe, cela limite les possibilités de se tromper.
À noter que le conseil de la section précédente de bien compartimenter les tests dans des scripts séparés prend maintenant tout son sens. L'autre avantage de la compartimentation est que cela limite le nombre de tests par script, facilitant par la-même leur décompte, quelle que soit la technique retenue.
Distribution CPAN
Petit rappel : quand on parle de « distribution CPAN », cela signifie qu'on parle d'une archive qui suit la structure et le processus d'installation classique (par Makefile.PL ou Build.PL), mais cela n'implique pas nécessairement de déposer cette archive sur le CPAN.
Ce qui rend l'écriture de tests si facile en Perl est la remarquable intégration des phases de test dans les cycles de développement et de déploiement des distributions CPAN, qui représentent le mécanisme d'installation le plus adapté pour les modules et programmes Perl.
Si l'exécution des tests lors de la phase de déploiement peut sembler redondante, il faut simplement se rendre compte qu'elle répond à une problématique différente. En effet l'exécution des tests pendant la phase de développement permet à la fois la validation du code écrit et un débogage facilité par la vérification constante de non-régression, comme cela a été expliqué dans le précédent article. Lors de la phase de déploiement du logiciel, les tests permettent de vérifier et de valider le fonctionnement de tout ou partie de ces fonctionnalités en conditions de production. Car, qu'il s'agisse de code fonctionnel ou de la suite de tests, il s'agit d'être sûr que tout cela fonctionne ailleurs que sur le poste du développeur !
Pendant le développement d'un module ou d'un programme Perl, plusieurs
moyens existent pour exécuter les scripts de tests. Le plus classique
est d'utiliser la commande make test
ou Build test
qui effectue
les étapes de compilation, si nécessaire, et de lancement de la suite
de tests. Lors de la compilation, les fichiers sources sont copiés dans
le sous-répertoire blib/, les fichiers Perl étant placés dans
l'arborescence blib/lib/ et les fichiers binaires dans blib/arch/.
La cible test
exécute ensuite les scripts de tests au travers de
Test::Harness
pour afficher le résumé habituel :
$ ./Build test lib/Module/ThirdParty.pm -> blib/lib/Module/ThirdParty.pm t/00load......ok 1/1# Testing Module::ThirdParty 0.16, Perl 5.008005, /usr/bin/perl5.8.5 t/00load......ok t/00prereq....# Checking required modules t/00prereq....ok t/01api.......ok t/10run.......ok t/distchk.....ok 1/10 skipped: Module::Build PREREQ_PM not yet implemented t/pod.........ok t/podcover....ok t/portfs......ok All tests successful, 1 subtest skipped. Files=8, Tests=43, 1 wallclock secs ( 1.12 cusr + 0.14 csys = 1.26 CPU)
C'est bien, mais si la suite de tests est longue, on perd du temps pour rien à exécuter toute la suite quand on sait que l'on ne travaille que sur une partie bien déterminée, et vérifiée par un script particulier.
Il suffit donc de n'exécuter que ce script spécifique, mais en pensant à ajouter au chemin de recherche les arborescences dans blib/ pour que les modules sur lesquels on travaille soient trouvés :
$ perl -wT -Iblib/lib -Iblib/arch t/10run.t 1..25 ok 1 - SVN::Core is a known third-party module ok 2 - CAIDA::NetGeoClient is a known third-party module ok 3 - Text::ChaSen is a known third-party module ...
Pour simplifier cette utilisation, on peut utiliser le module blib
(intégré à la distribution standard de Perl) qui réalise cela de manière
plus complète. Il a toutefois le défaut de ne pas être compatible avec
le mode teinté, d'où l'intérêt de savoir aussi comment faire sans lui.
$ perl -w -Mblib t/10run.t 1..25 ok 1 - SVN::Core is a known third-party module ok 2 - CAIDA::NetGeoClient is a known third-party module ok 3 - Text::ChaSen is a known third-party module ...
L'inconvénient dans l'utilisation de blib/ est qu'il faut par contre
penser à exécuter make
ou Build
avant de lancer le script, afin
de mettre à jour les fichiers dans ce répertoire. Pour les modules pur
Perl, sans partie à compiler comme du C ou de l'XS, on peut simplifier
le processus en incluant directement l'arborescence contenant les
fichiers sources de travail (si vous avez bien rangé vos modules dans
lib/, ce que vous devriez faire de toute façon) :
$ perl -wT -Ilib t/10run.t 1..25 ok 1 - SVN::Core is a known third-party module ok 2 - CAIDA::NetGeoClient is a known third-party module ok 3 - Text::ChaSen is a known third-party module ...
C'est pas mal, mais un dernier problème est que l'on voit chaque point
de test individuellement, sans le résumé bien pratique fourni par
Test::Harness
. Si le script comporte beaucoup de points de tests, ça
défile à toute vitesse et on n'a pas le temps de voir les erreurs. La
solution est de passer par Test::Harness
, mais son appel sur la ligne
de commande n'est pas des plus courts. Pour pallier à cela, Andy Lester
fournit avec les versions récentes de Test::Harness
l'utilitaire
prove qui permet justement d'exécuter un ou plusieurs scripts de test
au travers de ce module.
Ses options les plus intéressantes sont -b
et -l
qui ajoutent
respectivement blib/lib/ et lib/ au chemin de recherche,
-I
pour ajouter des chemins arbitraires, comme l'option de perl,
-t
et -T
pour activer le mode teinté en mode d'avertissement ou
en mode complet, -r
pour chercher les scripts récursivement.
$ prove -l t/10run.t t/10run....ok All tests successful. Files=1, Tests=25, 0 wallclock secs ( 0.06 cusr + 0.00 csys = 0.06 CPU)
Ainsi, si une erreur survient, elle devient bien plus facile à détecter :
$ prove -l t/10run.t t/10run....NOK 19 # Failed test ' - checking name' # in t/10run.t at line 33. # got: 'Chassen' # expected: 'ChaSen' # Looks like you failed 1 test of 25. t/10run....dubious Test returned status 1 (wstat 256, 0x100) DIED. FAILED test 19 Failed 1/25 tests, 96.00% okay Failed Test Stat Wstat Total Fail Failed List of Failed ----------------------------------------------------------------------- t/10run.t 1 256 25 1 4.00% 19 Failed 1/1 test scripts, 0.00% okay. 1/25 subtests failed, 96.00% okay.
Comme précédemment, si on utilise le répertoire blib/ il faut penser à
exécuter make
ou Build
juste avant.
La phase de déploiement correspond à l'installation du module ou du programme concerné. Celle-ci peut se réaliser manuellement par l'exécution des commandes habituelles :
$ perl Makefile.PL && make test && sudo make install
dans le cas d'une distribution utilisation ExtUtils::MakeMaker
ou
$ perl Build.PL && ./Build test && sudo ./Build install
si elle utilise Module::Build
. Toutefois, il est largement préférable
de passer par l'un des outils interactifs standards d'installation tel
que le shell CPAN.pm
ou par CPANPLUS. Ceux-ci offrent l'avantage
d'exécuter ces commandes, en ajoutant éventuellement des paramètres
supplémentaires pour tenir compte des spécificités de votre installation
(par exemple, l'ajout d'un PREFIX
). Mais leur principal intérêt de
ces shells est qu'il se chargent de résoudre et d'installer automatiquement
les dépendances du logiciel demandé. Il suffit de lancer l'installation
et de regarder l'écran défiler :-)
Pour l'installation de chaque module, le shell exécute la suite de tests et ne réalise l'installation que si ces derniers ont réussi. Le principe est de vérifier et de valider que l'ensemble des composants fonctionnent comme prévu en conditions de production. La suite de tests offre donc un certain degré de garantie de bon fonctionnement des composants et par la même du logiciel demandé. Ce degré est bien évident dépendant de la qualité de la suite de tests, un point qui sera présenté ultérieurement.
Avec CPANPLUS, et depuis quelques mois aussi avec CPAN.pm
, il est même
possible d'envoyer des rapports pour indiquer
si la suite de tests s'est correctement exécutée ou pas. Ces mails sont
collectés et analysés dans le cadre du projet CPAN Testers[5]. Les pages
de ce site sont pointées par les sites comme Search CPAN et Kobes' CPAN
Search depuis les pages correspondant aux distributions. Cela permet
ainsi d'évaluer la fiabilité et la portabilité d'un logiciel en vérifiant
si sa suite de test réussit ou pas sur les systèmes des contributeurs à
CPAN Testers.
Quand la suite de tests échoue lors du déploiement, il s'agit très souvent
des mêmes quelques causes classiques. Par exemple du fait de l'absence d'un
fichier nécessaire pour les tests, que le développeur a oublié de lister
dans MANIFEST, et qui n'est donc pas inclus dans la distribution.
Pour détecter au plus tôt ce type de problème, le développeur doit prendre
l'habitude d'exécuter la cible disttest
, qui réalise en partie une
simulation des tests en phase de déploiement. Plus exactement, disttest
crée la distribution dans sa forme finale, et se place dedans pour lancer
la compilation et les tests. Les problèmes comme les fichiers manquants
ou mal nommés dans MANIFEST, ou encore les erreurs de chemins relatifs
sont ainsi immédiatement mis en évidence.
Un autre problème classique est d'oublier d'ajouter un module à la liste des prérequis. L'aspect ennuyeux est que cela ne peut se détecter que quand on installe le logiciel sur une distribution Perl où ledit prérequis est absent. La solution pour le développeur est d'essayer de déployer son logiciel sur une installation neuve de Perl, afin de vérifier que le processus se déroule sans accroc. Cela implique donc qu'il doit recréer cette installation chaque fois qu'il veut tester son logiciel, ce qui est clairement fastidieux. Il est bien sûr possible d'automatiser cela, et c'est l'un des buts du projet PITA[6], initié par Adam Kennedy. Ce projet, assez ambitieux, a pour but de fournir une infrastructure logicielle permettant de tester un logiciel sur des systèmes et plates-formes variés en utilisant les fonctionnalités d'émulateurs tels que Qemu et VMware. Encore à ses débuts, PITA est néanmoins une future solution très prometteuse.
Un point important à noter est que tous les tests ne sont pas forcément adaptés ni même souhaitables en phase de déploiement, par exemple parce qu'ils vérifient des aspects secondaires comme la documentation ou la présence de fichiers de méta-données. Ils peuvent aussi nécessiter un environnement ou des outils spéciaux, qu'il n'y a pas de raison de mettre en place en production. Ces tests, s'ils sont utiles pour le développeur, ne le sont pas à l'installation du logiciel et n'ont donc pas à être exécutés lors de cette phase.
Il faut donc opérer une sélection des tests à exécuter en phase de déploiement, et disposer d'un moyen pour automatiser cette sélection. On distingue principalement trois méthodes pour ce faire.
La non-inclusion des scripts de test consiste tout simplement à ne pas les distribuer dans l'archive finale (il suffit de les retirer du fichier MANIFEST). Les scripts ne sont présents que sur la machine du développeur. C'est une solution adoptée par certains contributeurs du CPAN qui considèrent ainsi qu'ils évitent de participer à sa « pollution ». Toutefois, cela présente le défaut que si quelqu'un veut reprendre la maintenance d'un module, il ne dispose pas des scripts additionnels et il doit alors les demander au mainteneur précédent, quand c'est possible (cela peut être impossible pour des raisons pratiques, telle que la perte du disque dur, voire pour des causes plus graves comme la maladie ou le décès de la personne concernée). Pour cette raison, beaucoup d'autres développeurs préfèrent donc tous les inclure mais les désactiver en utilisant l'un des mécanismes suivants.
Le premier est l'exécution conditionnelle à la présence du module
de test, c'est-à-dire que le test n'est exécuté que si le module
nécessaire est présent. Cela a l'avantage d'éviter de devoir l'ajouter
à la liste des prérequis du paquet alors qu'il n'est utile que pour les
tests. La manière propre de gérer ça est d'utiliser skip_all
:
use strict; use Test::More; eval "use Test::Pod"; plan skip_all => "Test::Pod required for testing POD" if $@; # reste du script
On déplace le use
du module dans un eval
, ce qui permet de capturer
proprement toute erreur de chargement. S'il y en a une, on utilise l'option
skip_all
de la fonction plan()
pour signaler que ce script doit
être sauté. Sinon le reste du script est exécuté.
Le second est l'exécution conditionnelle à un facteur externe. Ce
dernier peut être la présence ou l'absence d'un fichier, d'un répertoire
d'une variable d'environnement, ou encore d'une clé de registre sous Win32.
Là aussi, on utilise skip_all
pour sauter le test. Par exemple, pour
n'exécuter un test que si une variable d'environnement est positionnée :
plan skip_all => "set TEST_POD to enable this test" unless $ENV{TEST_POD};
Autre exemple, tiré des scripts de Net::Pcap
:
use strict; use Test::More; use lib 't'; use Utils; plan skip_all => "must be run as root" unless is_allowed_to_use_pcap(); plan skip_all => "no network device available" unless find_network_device();
Ce script commence par ajouter le répertoire t/ dans le chemin de
recherche, puis charge le module Utils
, qui est justement dans ce
répertoire. Celui-ci fournit quelques fonctions destinées à faciliter
l'écriture des tests de Net::Pcap
.
La première utilisée, is_allowed_to_use_pcap()
, vérifie que l'utilisateur
qui exécute le script a le droit d'ouvrir l'interface réseau en mode direct,
la seconde, find_network_device()
recherche une interface réseau utilisable
pour les tests. Ces deux vérifications permettent de sauter le script si les
conditions nécessaires à son exécution ne sont pas remplies et permet ainsi
d'éviter des erreurs inutiles.
Maintenant que toutes les bases sur les tests en Perl vous ont été présentées et expliquées, nous continuerons cette série d'articles en explorant les modules de tests disponibles sur le CPAN pour ajouter des sémantiques de plus haut niveau.
[1] À noter qu'il s'agit du module écrit par Philippe Bruhat et qui est utilisé pour générer les articles des Mongueurs sur le site Articles.
[2] Créer une distribution pour le CPAN, Sébastien Aperghis-Tramoni, GNU/Linux Magazine France n°69, http://articles.mongueurs.net/magazines/linuxmag69.html
[3] Perl Best Practices - Damian Conway, O'Reilly & Associates, 2005, ISBN 0-596-00173-8, http://www.oreilly.com/catalog/perlbp/ ; Disponible en français sous le titre De l'art de programmer en Perl (O'Reilly 2006, ISBN 2-84177-369-8).
[4] QA Podcasts - http://www.qapodcast.com/
[5] CPAN Testers - http://testers.cpan.org/
[6] PITA - http://ali.as/pita/, http://search.cpan.org/dist/PITA/
Sébastien Aperghis-Tramoni <sebastien@aperghis.net>, "Maddingue" - {Marseille,Sophia}.pm
Sébastien Aperghis-Tramoni est administrateur systèmes dans le Sud de la France. Il maintient par ailleurs une vingtaine de modules sur le CPAN et essaye d'aider au développement de Perl en fournissant des services comme Perl cover et un Gonzui du CPAN.
Philippe Blayo - Paris.pm
Merci aux Mongueurs qui ont assuré la relecture de cet article.
Copyright © Les Mongueurs de Perl, 2001-2011
pour le site.
Les auteurs conservent le copyright de leurs articles.