-+  Documenti
 |-  Bibliografia
 |-  Articoli
 |-  Perlfunc
 |-  F.A.Q.
 |-  F.A.Q. iclp
-+  Eventi
-+  Contatti
-+  Blog
-+  Link



 

Versione stampabile.


Flavio Poletti
Consulente in telecomunicazioni per una piccola società a Roma, usa Perl per lavoro e per diletto, con particolare attenzione all'automazione di processi ripetitivi e noiosi.
www.polettix.it

Lo sviluppo di un software, a qualunque livello non banale, comporta normalmente differenti cicli o, comunque, differenti fasi. Quante, e soprattutto quali, siano queste fasi è stato negli anni precedenti oggetto di un vivace dibattito, dal quale sembrano essere uscite vincenti le tecniche di sviluppo agile (agile development) la più nota delle quali è probabilmente quella che va sotto il nome di XP - eXtreme Programming.

Quello su cui probabilmente c'è accordo è il fatto che il software ha bisogno di essere sottoposto continuamente a verifiche. Questo è qualcosa che chiunque con un minimo di esperienza di programmazione ha provato da sé: facciamo un po' di codice e proviamo se funziona. Mentre però potremmo essere portati ad un approccio un po' naive a questo tipo di prove, facendole magari con un po' di fretta e dimenticandocene una volta che va tutto bene, il buon senso punta ad avere un approccio un po' più sistematico ed organizzare questi test per poterli ripetere con regolarità.

Da tutto ciò nasce il concetto di test suite, ossia di quell'insieme di strumenti (normalmente script) predisposti unicamente alla verifica che le differenti parti del software stiano funzionando come ci si aspetta. Tali test possono essere divisi in più parti: dovremmo infatti avere test per ogni singola parte del software (ogni modulo, ad esempio), e test più ampi, che verifichino l'integrazione dei moduli fra di loro. Scrivere i test non è un lavoro facile, e potrebbe risultare noioso perché, in realtà, si sta sviluppando qualcosa che non serve direttamente al problema che il software dovrebbe risolvere.

Il nostro problema, però, non è lo stesso del software. Se stiamo realizzando un processore di testo, il software deve processare il testo (sic!), noi dobbiamo sviluppare il software. Per questo motivo, dobbiamo mettere nella faretra tutte le frecce che sono necessarie affinché questo lavoro di sviluppo sia semplice, il più possibile privo di errori e, soprattutto, manutenibile in futuro. Perché una cosa è sicura: i bug ci saranno, per sottili e insignificanti che possano apparire.

Quando scrivere i test?

L'approccio XP prevede che i test siano scritti prima di scrivere il relativo pezzo di software. Può sembrare strano, ma è così. In realtà, si va molto oltre: occorre codificare solo ciò che permette di passare i test. In pratica:

  • scrivo il test per la parte che devo codificare;
  • lo lancio senza aver sviluppato niente altro;
  • riscontro un errore (che sorpresa!);
  • sviluppo il minimo codice necessario a passare il test.

Secondo questa definizione, una feature non esiste se non c'è un test che la verifichi. Devo ammettere che ancora non mi sono abituato a questo stile: di solito sono molto più curioso di scrivere il codice per vedere se risolve il problema, e lascio i test a dopo. Probabilmente, però, sbaglio: a quel punto, scrivere i test diventa una noia mortale!

Come organizzare i test in Perl?

Come detto prima, i test dovrebbero cercare di coprire il più possibile quanto state sviluppando. In generale, se scrivete un po' di codice che non è testabile, dovreste ripensarlo per fare in modo che lo sia; questo vi risparmierà tempo e fatica quando salterà fuori qualche bug in futuro.

Perl mette a disposizione una serie di moduli preposti specificamente alle attività di test. Qui vedremo solamente due di essi, peraltro molto legati: Test::Simple ed il più evoluto Test::More.

La base: Test::Simple

Ormai nessuno, probabilmente, utilizza più Test::Simple per un nuovo progetto. Per il semplice motivo che è troppo Simple. In ogni caso, per partire va più che bene.

La prima cosa da fare è... usarlo:

use Test::Simple tests => 3;

In fase di utilizzo, dobbiamo dire quanti test abbiamo intenzione di fare. Perché? È piuttosto semplice: per avere un riscontro quando, per qualche motivo, lo script sotto test muore prima del tempo. Se lo fa, vuol dire che effettua meno test e possiamo controllarlo:

#!/usr/bin/perl
use strict;
use warnings;
use Test::Simple tests => 3;
 
ok( 2 + 3 == 5, "somma");
ok( 2 * 2 == 5, "moltiplicazione");
die("non voglio!");
ok( 6 / 3 == 2, "divisione");

__END__


1..3
ok 1 - somma
not ok 2 - moltiplicazione
#     Failed test (simple01.pl at line 7)
non voglio! at simple01.pl line 8.
# Looks like you planned 3 tests but only ran 2.
# Looks like your test died just after 2.

Stiamo dichiarando 3 test, solo che chiamiamo die prima dell'ultimo di essi. Che succede? Semplice:

  • Test::Simple sa che dobbiamo fare 3 test, per questo motivo scrive 1..3, come a dire: faccio i test da 1 a 3;
  • si comincia con i vari test: se vanno bene stampa ok con la descrizione che abbiamo dato del test, altrimenti stampa not ok con l'indicazione di dove si trova il test fallito. Per andare bene si intende che il primo argomento della chiamata alla funzione ok() è un valore booleano "vero";
  • quando viene chiamato die(), stampa un messaggio di avvertimento sul fatto che dei 3 test programmati ne sono stati eseguiti solo 2 - questo è un campanello di allarme!

Cosa succede quando va tutto fino in fondo, invece? Presto detto:

#!/usr/bin/perl
use strict;
use warnings;
use Test::Simple tests => 3;
 
ok( 2 + 3 == 5, "somma");
ok( 2 * 2 == 5, "moltiplicazione");
ok( 6 / 3 == 2, "divisione");
 
__END__
 
1..3
ok 1 - somma
not ok 2 - moltiplicazione
#     Failed test (simple02.pl at line 7)
ok 3 - divisione
# Looks like you failed 1 tests of 3.

Piuttosto semplice: ci stampa una riga finale di riassunto. Nel caso vada tutto bene è più silenzioso:

#!/usr/bin/perl
use strict;
use warnings;

use Test::Simple tests => 3;
 
ok( 2 + 3 == 5, "somma");
ok( 2 * 2 == 4, "moltiplicazione");
ok( 6 / 3 == 2, "divisione");
 
__END__
 
1..3
ok 1 - somma
ok 2 - moltiplicazione
ok 3 - divisione

Come a dire: nessuna nuova, buona nuova. Siamo già in grado di scrivere i test!

Mettere le briglie ai test

Se abbiamo un solo file di test, tutto procede abbastanza semplicemente: basta lanciarlo ed aspettare di vedere se c'è un rapporto finale oppure no. Quando però i test diventano parecchi, c'è il rischio di perdersi se qualche file ha dato errore oppure no; per questo motivo è stato creato Test::Harness, il modulo che mette le briglie ai test.

La cosa bella è che, in generale, non avrete bisogno di utilizzarlo direttamente: se fate partire i vostri progetti di nuovi moduli utilizzando h2xs questo script fa tutto per voi. È però interessante capire cosa fa e cosa ci si può aspettare quando viene utilizzato dietro le quinte!

Per chiamare tutti insieme i nostri script (supponendo siano tutti i "pl" nella directory corrente):

perl "-MTest::Harness" "-e" "runtests(@ARGV)" *.pl

Recuperando i test introdotti in precedenza otteniamo quanto segue:

simple01....#     Failed test (simple01.pl at line 7)
non voglio! at simple01.pl line 8.
# Looks like you planned 3 tests but only ran 2.
# Looks like your test died just after 2.
simple01....dubious
        Test returned status 255 (wstat 65280, 0xff00)
DIED. FAILED tests 2-3
        Failed 2/3 tests, 33.33% okay
simple02....#     Failed test (simple02.pl at line 7)
# Looks like you failed 1 tests of 3.
simple02....dubious
        Test returned status 1 (wstat 256, 0x100)
DIED. FAILED test 2
        Failed 1/3 tests, 66.67% okay
simple03....ok
Failed Test Stat Wstat Total Fail  Failed  List of Failed
-------------------------------------------------------------------------------
simple01.pl  255 65280     3    3 100.00%  2-3
simple02.pl    1   256     3    1  33.33%  2
Failed 2/3 test scripts, 33.33% okay. 3/9 subtests failed, 66.67% okay.

L'uscita è meno loquace rispetto a prima: in particolare, vengono riportate solamente le condizioni di errore trovate o, in caso di successo, un semplice ok per il singolo file (come nel caso di simple03.pl).

La parte interessante si ha però nelle statistiche finali. Queste sono quello che dà il vero colpo d'occhio sui test: quanti sono andati bene? Quanti male, e dove? Bene, questa tabellina ci dà un volo d'uccello sui test eseguiti, mettendoci in condizione di approfondire nel caso qualcosa vada male.

Andiamo avanti: Test::More

Se avete giocato un po' con Test::Simple (l'avete fatto, vero?) avrete notato che molte cose le dovete ancora fare voi. Inoltre, i test non sono proprio leggibilissimi: occorre associare ad ogni test una condizione booleana, il che appiattisce i test.

Bene, allora, cosa si testa di solito? Proviamo a fare una rassegna:

  • inclusione di un modulo: anche la compilazione di un modulo è importante, per catturare errori di battitura e quant'altro (specialmente se si lavora con strict). Sarebbe comodo avere un costrutto dedicato a questo tipo di test;
  • valore restituito da una funzione: molte volte scriviamo funzioni il cui valore restituito è un segnale se qualcosa è andato storto. Per questo, ok() se la cava già egregiamente;
  • uguaglianza fra valori: tutti i test che abbiamo fatto prima controllavano l'uguaglianza fra due espressioni, sarebbe comodo (e leggibile) avere una funzione che lo fa direttamente;
  • uguaglianza fra strutture: cosa succede se abbiamo strutture dati complesse? Dobbiamo implementare una visita delle due strutture da comparare - che noia!

A tutto ciò viene incontro Test::More. Prima di tutto, però, inventiamoci un modulo completamente inutile da testare:

package Inutile;
use strict;
use warnings;
 
require Exporter;
our @ISA = qw( Exporter );
our @EXPORT = qw( sempre_vera sempre_falsa per_due in_hash );
 
sub sempre_vera { return 1 }
sub sempre_falsa { return 0 }
 
sub per_due { return 2 * $_[0] }
sub in_hash { return { @_ } } 
1;

È un modulo semplice ed inutile, come indica chiaramente il nome. Vanta ben 1e12 tentativi di imitazione, comunque. Per semplicità esporta tutte le funzioni contenute (e questa non è di solito una buona idea), che sono:

  • sempre_vera(): come dice il nome, restituisce un valore che è sempre vero. Utile casomai dovessimo scordarcene;
  • sempre_falsa(): come sopra, solo che il valore è sempre falso;
  • per_due(): opera su un solo argomento in ingresso, moltiplicandolo per due e restituendolo in uscita;
  • in_hash(): utilizza i parametri in ingresso alternativamente come chiavi e come valori di un hash, della quale restituisce un riferimento.

Il file di test relativo a questo modulo è il seguente:

#!/usr/bin/perl
use Test::More 'no_plan';
 
BEGIN {
   use_ok('Inutile');
}
 
# Test per un valore vero
ok(sempre_vera(), "funzione sempre_vera()");
 
# Test per un valore falso
ok(!sempre_falsa(), "funzione sempre_falsa()");
 
# Test che il valore restituito sia quello atteso
is(per_due(5), 10, "per_due()");
isnt(per_due(3), 7, "per_due(), test negativo");
 
# Test su una struttura
is_deeply(in_hash('ciao', 'a', 'tutti', 'quanti'),
   {ciao => 'a', tutti => 'quanti'}, 'in_hash()');

Si noti che, in fase di use, non viene più specificato il numero di test da eseguire, ma si dichiara che non si ha un piano preciso. Questa opzione è comoda durante lo sviluppo dei test, poiché si tende ad aggiungerne di nuovi molto spesso, ma andrebbe sostituita dal sistema di indicazione descritto in precedenza per far sì da individuare problemi relativi a morti premature degli script.

In dettaglio:

  • use_ok() ci aiuta a capire se ci sono errori di compilazione nel modulo. Provate a modificare il modulo inserendo un errore di sintassi, e vedete cosa succede. L'inclusione all'interno di un blocco BEGIN è necessaria perché la chiamata avvenga in fase di compilazione e non in fase di run dello script di test;
  • ok() è una vecchia conoscenza, e non è cambiato di una virgola nel suo utilizzo;
  • is() consente di esprimere in maniera leggibile il vincolo che due cose siano uguali fra di loro. La prima è quanto otteniamo da un'espressione, funzione o quant'altro, la seconda rappresenta ciò che ci aspettiamo. Si noti che questo test di uguaglianza vale solo per scalari!
  • isnt() permette di dire che qualcosa non è uguale a qualcos'altro. Il perché lo sapete solo voi…
  • is_deeply() effettua per voi il confronto fra due strutture complesse. Il funzionamento è analogo a quello di is(), solo che occorre passare due riferimenti alle strutture da controllare. La verifica di uguaglianza si spinge, ovviamente, a qualunque livello di profondità.

Insomma, di frecce ne abbiamo ora, basta allenarsi e saremo anche noi dei piccoli Robin Hood.

Concludendo

Spero che questa breve panoramica sul test vi abbia convinto che scrivere i test è una cosa utile e, in Perl, relativamente semplice. Test::More va ovviamente oltre quanto brevemente descritto sopra, un'occhiata al manuale vi convincerà. Se poi non ne avete abbastanza, CPAN contiene miriadi di moduli per il testing - c'è solo l'imbarazzo della scelta...


Ti è piaciuto questo articolo? Iscriviti al feed!











Devo ricordare i dati personali?






D:
Annunci Google