2012-10-19 7 views
10

Questa è la prima domanda in Stack Overflow. Mi scuso in anticipo se sto violando alcune regole.Test del modulo Perl con Test :: Altro (Perl intermedio, capitolo 14)

Ho letto il capitolo 14 di Intermediate Perl, 2 ° ed., Che tratta i moduli Perl di test e utilizza le funzionalità di Test :: Altro. Mi riferisco al codice pubblicato direttamente in questo libro nella sezione intitolata "Aggiungere i nostri primi test".

Per alcuni sfondi, in questo capitolo viene creata una classe campione Animal in un modulo con lo stesso nome. Questa classe ha un metodo semplice speak che assomiglia a questo:

sub speak { 
    my $class = shift; 
    print "a $class goes ", $class->sound, "!\n"; 
} 

Il metodo sound è una semplice stringa restituita per un animale particolare, quindi, ad esempio, il metodo di un cavallo sound sarà semplicemente sub sound { "neigh" } ed è speak metodo dovrebbe uscita il seguente:

A Horse goes neigh! 

il problema che sto funzionando in è il seguente: nel codice di test che ho creato in ./Animal/t/Animal.t, io sono incaricato di utilizzare i blocchi nude e Test::More::is a prova che il speak metodo funziona. Il codice è simile al seguente nel file di test:

[test code snip] 
{ 
    package Foofle; 
    use parent qw(Animal); 

    sub sound { 'foof' } 
    is(Foofle->speak, 
     "A Foofle goes foof!\n", 
     "An Animal subclass does the right thing" 
    ); 
} 

Il test ha esito negativo. Ho eseguito tutti i comandi di compilazione, ma durante l'esecuzione di "test build", ottengo questo fallimento per il test sugli animali:

Undefined subroutine &Foofle::is called at t/Animal.t line 28. 

Quando provo utilizzare in modo esplicito Test::More::is invece di semplicemente is, il test fallisce ancora con la seguente messaggio:

# Failed test 'An Animal subclass does the right thing' 
# at t/Animal.t line 28. 
#   got: '1' 
#  expected: 'A Foofle goes foof! 
# ' 

I miei metodi sembrano definiti esattamente come ho spiegato. Penso che il primo errore sia un problema di ambito a causa dei blocchi nudi, ma non sicuro al 100%. Il secondo errore non sono sicuro, perché se dovessi creare una classe come figlio di Animal e chiamato speak su di esso, non otterrei una risposta 1, ma piuttosto l'output atteso.

Qualcuno sarebbe in grado di dare una mano su quello che potrei fare di sbagliato? Per versioni software forse rilevanti, sto usando perl v5.16, Test :: Altro v0.98 e Module :: Starter v1.58.

+4

una domanda fine, ben formata e contenente dettagli appropriati. benvenuti a SO –

risposta

5

Hai spiegato correttamente il motivo del primo errore e risolto correttamente (specificando il nome del pacchetto corretto). Ma sembra che manchi il semplice fatto: il metodo speak della classe Animal non è return questa stringa a $class goes... - restituisce invece il risultato della stampa (che è 1)!

See, questa subroutine:

sub speak { 
    my $class = shift; 
    print "a $class goes ", $class->sound, "!\n"; 
} 

... non ha un esplicito return dichiarazione. In questo caso, restituito è il risultato della valutazione dell'ultima istruzione invocata della subroutine, ovvero il risultato della valutazione di print something, che is1 (true, in realtà).

Ecco perché il test ha esito negativo.È possibile risolvere il problema con il test per 1 (ma è troppo banale, suppongo) o modificando il metodo stesso in modo che restituisca una stringa che stampa. Ad esempio:

sub speak { 
    my $class = shift; 
    my $statement = "a $class goes " . $class->sound . "!\n"; 
    print $statement; 
    return $statement; 
} 

... e, francamente, entrambi gli approcci sembrano un po '... di pesce. Quest'ultima, anche se ovviamente più completa, in realtà non coprirà tutte le funzionalità di questo metodo speak: verifica se la dichiarazione è corretta o meno, ma non se è stata stampata o meno.)

+0

Interessante, grazie! Dovrei restituire la stringa invece di stamparla. Devo ricontrollare, ma penso che questo si qualificherà come errata per questo libro. – rsa

2

sto immaginando il codice sia qualcosa di simile:

package SomeTest; # if omitted, it's like saying "package main" 
use Test::More; 
... 
{ 
    package Foofle; 
    is(something, something_else); 
} 

La dichiarazione use Test::More esporterà alcune delle funzioni s' il Test::More allo spazio dei nomi di chiamata, in questo caso SomeTest (o main). Ciò significa che saranno definite le funzioni per i simboli main::is, main::ok, main::done_testing, ecc

Nel blocco che inizia con package Foofle, ora siete nel Foofle spazio dei nomi, così ora Perl cercherà una funzione che corrisponde al simbolo Foofle::is. Non ne troverà uno, quindi si lamenterà e uscirà.

Una soluzione alternativa è di importare Test::More nello spazio dei nomi .

{ 
    package Foofle; 
    use Test::More; 
    is(something, something_else); 
} 

e un altro è quello di utilizzare un nome di metodo completo di chiamare is:

{ 
    package Foofle; 
    Test::More::is(something, something_else); 
} 
4

Hai già lavorato fuori che il problema con il vostro invito is era che si erano nel torto pacchetto nel momento in cui hai effettuato la chiamata. Completamente specificando il nome della funzione come avete lavori realizzati, come fa l'importazione is nel vostro spazio dei nomi dicendo

use Test::More; 

da qualche parte nel pacchetto con il test.

La risposta al resto della domanda si trova nella differenza tra ciò che si sta testando e ciò che si sta facendo. Cosa speakfa è la stampa, ma quando si chiede is(speak, ...), si chiede su cosa restituisce speak, che non è correlato a ciò che è stato stampato. È infatti il ​​valore di ritorno non molto utile di print.

Poiché lo scopo di speak è di stampare una stringa particolare, un test per speak deve verificare che abbia effettivamente stampato una stringa e che sia stata la stringa corretta. Per fare il test, però, è necessario un modo per catturare ciò che è stato stampato.

ci sono in realtà un paio di modi per farlo, usare i IO::File per forza di specificare un handle di file a cui stampare scimmia patching un sostituto per print in voi di classe, ma la seguente tecnica non richiede qualsiasi modifica al sistema in prova per migliorare la sua testabilità.

Il select integrato consente di modificare il punto di stampa print. Il canale di output predefinito è STDOUT, anche se generalmente dovresti fingere di non sapere quel tipo di cose.Fortunatamente, puoi anche usare select per scoprire l'handle del file originale, anche se probabilmente dovresti assicurarti di ripristinare l'handle di file predefinito (che è, dopo tutto, una variabile globale) anche se il test muore per qualche motivo. Quindi è necessario gestire le eccezioni. E hai bisogno di un handle di file puoi controllare il contenuto di e non necessariamente stampare effettivamente qualcosa; IO::Scalar può aiutare lì.

Con questo approccio, si finisce per essere in grado di testare il codice originale con

package AnimalTest; 
use IO::Scalar; 

use Test::More tests => 1; 
use Try::Tiny; 

{ 
    package Foofle; 
    use base qw(Animal); 

    sub sound { 'foof' } 
} 

{ 
    my $original_FH = select; 
    try { 
     my $result; 
     select IO::Scalar->new(\$result); 

     Foofle->speak(); 
     is(
      $result, "A Foofle goes foof!\n", 
      "An Animal subclass does the right thing" 
     ); 
    } catch { 
     die $_; 
    } finally { 
     select $original_FH; 
    }; 
} 

Try::Tiny è fare in modo che non venga disperso se speak accade per dare al Animal un aneurisma, print viene reindirizzato da modificare uno scalare invece di stampare effettivamente sullo schermo, e ora il test fallisce per il giusto motivo, per esempio: le stringhe hanno una capitalizzazione errata.

Noterai che è necessario un sacco di configurazione; questo perché il sistema in prova non è particolarmente ben impostato per la testabilità e quindi dobbiamo compensare. Nel mio codice, questo non è l'approccio che sceglierei, preferirei invece rendere più verificabile il codice originale. Quindi, per il test, eseguo il patch delle scimmie (ad esempio, sovrascrivo uno dei metodi in prova), spesso utilizzando TMOE. Questo approccio sembra più simile a questo:

[in Animal:]

sub speak { 
    my $class = shift; 
    $class->print("a $class goes ", $class->sound, "!\n"); 
} 

sub print { 
    my $class = shift; 
    print @_; 
} 

[tardi:]

{ 
    package Foofle; 
    use base qw(Animal); 

    sub sound { 'foof' } 

    sub print { 
     my ($self, @text) = @_; 

     return join '', @text; 
    } 

} 

is(
    Foofle->speak(), "A Foofle goes foof!\n", 
    "An Animal subclass does the right thing" 
); 

Si noterà che questo sembra molto più come il tuo codice originale. La differenza principale è che invece di chiamare direttamente il print integrato, chiama $class->print, che a turno chiama print integrato. La sottoclasse sovrascrive il metodo print per restituire gli argomenti anziché stamparli, il che dà al codice di prova l'accesso a ciò che avrebbe stampato.

Questo approccio è molto più pulito rispetto alla necessità di modificare i globali al fine di capire cosa viene stampato, ma presenta due svantaggi: richiede la modifica del codice sotto test per renderlo più testabile e non verifica mai effettivamente se la stampa accade. Semplicemente verifica che print sia stato chiamato con gli argomenti giusti. È quindi imperativo che Animal :: print sia così banale da essere ovviamente corretto da un'ispezione.

+0

Questa risposta si è rivelata più lunga di quanto intendessi, poiché ho scoperto che volevo presentare entrambe le alternative. – darch

Problemi correlati