[couverture de Linux Magazine 89]

Tests et Perl - Bonnes pratiques

Article publié dans Linux Magazine 89, décembre 2006.

Copyright © 2006 - Sébastien Aperghis-Tramoni, Philippe Blayo

[+ del.icio.us] [+ Developers Zone] [+ Bookmarks.fr] [Digg this] [+ My Yahoo!]

Chapeau de l'article

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.

Bonne gestion des tests

Voyons d'abord quelques bonnes pratiques de gestion des scripts de tests.

Qualité du code

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.

Compartimenter ses tests

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 :

Dans le cas d'un module plus complexe comme Net::Proxy, la suite de tests doit aussi réaliser des tests plus élaborés.

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 :

Pourquoi compter ses tests

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.

Techniques de comptages

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

[couverture du livre]

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.

Intégration au cœur du processus

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 !

Phase de développement

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.

Phase de déploiement

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.

Sélection des tests

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.

Conclusion

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.

Références

[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/

Auteurs

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.

[IE7, par Dean Edwards] [Validation du HTML] [Validation du CSS]