2016-04-25 4 views
0

Dies ist nur ein Beispiel für einen der Tests, die ich bei einem Fehler ausführe. Ich möchte das bearbeiten, damit ich eine Liste von Fehlern durchlaufen kann. Würde ich diese Fehler in einen Hash setzen und eine for-Schleife erstellen, um sie zu durchlaufen. Ich bin mir nicht sicher, wie es genau gemacht wird. Ich werde den Test und die Fehlerbibliothek unten anzeigen. Ich brauche nur ein kleines Beispiel, um mich zum Laufen zu bringen.Wie würde ich diesen Code ändern, damit ich ihn durch eine Fehlerliste iterieren und testen kann?

Testdatei:

use lib('./t/lib/'); 
use Test::More tests => 3; 
use ASC::Builder:Error; 
######################################################################################################### 
############## test for new() method in Error.pm - Test Case: HASH #################################### 
######################################################################################################### 



# error hash 
my $error_hash = UNABLE_TO_PING_SWITCH_ERROR; 

# error hash is passed into new and an error object is outputted 
my $error_in = ASC::Builder::Error->new($error_hash); 

# checks to see if the output object from new is an Error object 
isa_ok($error_in, 'ASC::Builder::Error'); 

# checking that object can call the message() method 
can_ok($error_in, 'message'); 


# checks to see if the output message matches the message contained in the error hash(correct) 
is($error_in->message(),($error_hash->{message}), 'Returns correct error message'); 

ErrorLibrary.pm

package ASC::Builder::ErrorLibrary; 

use strict; 
use warnings; 
use parent 'Exporter'; 

# list of export error messages 
our @EXPORT_OK = qw/ 

INCORRECT_CABLING_ERROR 
UPDATE_IMAGE_ERROR 
UNABLE_TO_PING_SWITCH_ERROR 
/; 

# error message list 

use constant { 
    # wiki link included as a variable in this example 
    INCORRECT_CABLING_ERROR => { 
     code => "INCORRECT_CABLING_ERROR", 
     errorNum => 561, 
     category => 'Cabling Error', 
     message => "ToR cabling is not correct at T1.The uplinks must be cabled to exactly one t1 device group", 
     tt => { template => 'disabled'}, 
     fatal => 1, 
     wiki_page =>'http://w.server-build.com/index.phpBuilder/ErrorCodes/INCORRECT_CABLING_ERROR', 
    }, 

    UPDATE_IMAGE_ERROR => { 
     code => "UPDATE_IMAGE_ERROR", 
     errorNum => 556, 
     category => 'Switch Error', 
     message => "Cannot determine switch model", 
     tt => { template => 'disabled'}, 
     fatal => 1, 
     wiki_page =>'http://www.server-build.com/index.php/NetMgmt/Builder/ErrorCodes/UPDATE_IMAGE_ERROR', 
    }, 

    UNABLE_TO_PING_SWITCH_ERROR => { 
     code => "UNABLE_TO_PING_SWITCH_ERROR", 
     errorNum => 727, 
     category => 'Switch Error', 
     message => "Could not ping switch [% switch_ip %] in [% timeout %] seconds.", 
     tt => {template => 'disabled'}, 
     fatal => 1, 
     wiki_page => 'http://www.server-build.com/index.php/Builder/ErrorCodes/UNABLE_TO_PING_SWITCH_ERROR', 
    }, 

    UNKNOWN_CLIENT_CERT_ID_ERROR => { 
     code => "UNKNOWN_CLIENT_CERT_ID_ERROR", 
     errorNum => 681, 
     category => 'Services Error', 
     message => "Unknown client certificate id: [% cert_id %]", 
     tt => { template => 'disabled'}, 
     fatal => 1, 
     wiki_page =>'http://www.server-build.com/index.php/Builder/ErrorCodes/UNKNOWN_CLIENT_CERT_ID_ERROR', 
    }, 


# add errors to this library  
}; 


1; 

Ich bin nur nicht sicher, wie meine Liste zu erstellen. Soll ich eine Liste von mit Eingabe, Prozess und Ausgaben erstellen, um zu testen. Ich bin ein bisschen verwirrt.

+0

Bitte klären Sie, was das Problem ist. Fragen Sie, wie Sie den Komponententest strukturieren, um Code-Duplikation zu vermeiden? Du sprichst von "Prozess", was von deinen vorherigen Fragen, glaube ich, eine Methode ist. Warum sollte das in den Komponententest gehen? – simbabque

+0

Nein, ich möchte nur wissen, wie ich meinen Test einrichten kann, so dass ich, anstatt einen Fehler testen zu können, eine for-Schleife oder ähnliches verwenden kann, um die Tests für mehrere Fehler auszuführen. Ich wollte den Wortprozess nicht hineinbringen. Ich dachte nur, dass ich eine Liste mit 'data = erstellen sollte [input => UNABLE_TO_PING_SWITCH_ERROR, Ausgabe =>" Switch 192.192.0.0 konnte in 30 Sekunden nicht gepingt werden]; 'Wäre es möglich, so etwas für jeden Fehler zu tun, setzen in eine Anordnung von Daten und Schleife durch sie –

+0

bekommen Sie, was ich versuche ich werde den falschen Weg, um es zu tun? Vielleicht? –

Antwort

1

Nach einer langen Diskussion im Chat, schlage ich den folgenden Ansatz für Unit-Tests vor, sowie eine leichte Umstrukturierung des eigentlichen Codes, um die Dinge ein wenig einfacher zu machen.

Änderungen, die ich

ich die Art und Weise umstrukturiert der Code die Fehlermeldung aus einer Vorlage erstellt nicht Template verwenden, da es von your previous question klar war, dass es ein bisschen übertrieben ist.

Es verwendet jetzt sprintf mit einem einfachen Muster wie Timeout after %s seconds. Ich habe %s in meinen Beispielen absichtlich verwendet, da es in diesen keine Typüberprüfung gibt, aber es könnte natürlich hinzugefügt werden. Die Argumente für diese Nachricht werden dem Konstruktor als eine Liste von Schlüssel/Wert-Paaren übergeben, die mit dem 2. Argument beginnen.

my $e = Error->new(CONSTANT, foo => 'bar'); 

Das Beispiel ErrorLibrary

Das erste Argument CONSTANT noch aus Ihrer Fehler Bibliothek kommt. Ich habe die folgenden vereinfachten Beispiele aufgenommen.

package ErrorList; 
use strict; 
use warnings; 
use parent 'Exporter'; 
use constant { 
    ERROR_WIFI_CABLE_TOO_SHORT => { 
     category => 'Layer 1', 
     template => 'A WiFi cable of %s meters is too short.', 
     context => [qw(length)], 
     fatal  => 1, 
     wiki_page => 'http://example.org', 
    }, 
    ERROR_CABLE_HAS_WRONG_COLOR => { 
     category => 'Layer 1', 
     template => 'You cannot connect to %s using a %s cable.', 
     context => [qw(router color)], 
     fatal  => 1, 
     wiki_page => 'http://example.org', 
    }, 
    ERROR_I_AM_A_TEAPOT => { 
     category => 'Layer 3', 
     template => 'The device at %s is a teapot.', 
     context => [qw(ip)], 
     fatal  => 0, 
     wiki_page => 'http://example.org', 
    }, 
}; 

our @EXPORT = qw(
    ERROR_WIFI_CABLE_TOO_SHORT 
    ERROR_CABLE_HAS_WRONG_COLOR 
    ERROR_I_AM_A_TEAPOT 
); 

our @EXPORT_OK = qw(ERROR_WIFI_CABLE_TOO_SHORT); 

Der Kontext ist eine Array-Referenz mit einer Liste von Schlüsseln, die bei Konstruktion zu erwarten sind.

Die umstrukturierte (vereinfacht) Fehlerklasse

Diese Klasse umfasst POD zu erklären, was es tut. Die wichtigsten Methoden sind der Konstruktor message und stringify.

package Error; 
use strict; 
use warnings; 

=head1 NAME 

Error - A handy error class 

=head1 SYNOPSIS 

use Error; 
use ErrorList 'ERROR_WIFI_CABLE_TOO_SHORT'; 

    my $e = Error->new(
     ERROR_WIFI_CABLE_TOO_SHORT, 
     timeout => 30, 
     switch_ip => '127.0.0.1' 
    ); 
    die $e->stringify; 

=head1 DESCRIPTION 

This class can create objects from a template and stringify them into a 
log-compatible pattern. It makes sense to use it together 
with L<ErrorList>. 


=head1 METHODS 

=head2 new($error, %args) 

The constructor takes the error definition and a list of key/value pairs 
with context information as its arguments. 

... 

=cut 

sub new { 
    my ($class, $error, %args) = @_; 

    # initialize with the error data 
    my $self = $error; 

    # check required arguments... 
    foreach my $key (@{ $self->{context} }) { 
     die "$key is required" unless exists $args{$key}; 

     # ... and take the ones we need 
     $self->{args}->{$key} = $args{$key}; # this could have a setter 
    } 

    return bless $self, $class; 
} 

=head2 category 

This is the accessor for the category. 

=cut 

sub category { 
    return $_[0]->{category}; 
} 

=head2 template 

This is the accessor for the template. 

=cut 

sub template { 
    return $_[0]->{template}; 
} 

=head2 fatal 

This is the accessor for whether the error is fatal. 

=cut 

sub is_fatal { 
    return $_[0]->{fatal}; 
} 

=head2 wiki_page 

This is the accessor for the wiki_page. 

=cut 

sub wiki_page { 
    return $_[0]->{wiki_page}; 
} 

=head2 context 

This is the accessor for the context. The context is an array ref 
of hash key names that are required as context arguments at construction. 

=cut 

sub context { 
    return $_[0]->{context}; 
} 

=head2 category 

This is the accessor for the args. The args are a hash ref of context 
arguments that are passed in as a list at construction. 

=cut 

sub args { 
    return $_[0]->{args}; 
} 

=head2 message 

Builds the message string from the template. 

=cut 

sub message { 
    my ($self) = @_; 

    return sprintf $self->template, 
     map { $self->args->{$_} } @{ $self->context }; 
} 

=head2 stringify 

Stringifies the error to a log message, including the message, 
category and wiki_page. 

=cut 

sub stringify { 
    my ($self) = @_; 

    return sprintf qq{%s : %s\nMore info: %s}, $self->category, 
     $self->message, $self->wiki_page; 
} 

=head1 AUTHOR 

simbabque (some guy on StackOverflow) 

=cut 

Die tatsächliche Einheit testet

Nun dies zu testen, ist es wichtig, zwischen Verhalten und Daten zu unterscheiden. Das Verhalten enthält alle Accessoren, die im Code definiert sind, sowie die interessanteren Subs wie new, message und stringify.

Der erste Teil der Testdatei, die ich für dieses Beispiel erstellt habe, enthält diese.Es schafft eine gefälschte Fehlerstruktur $example_error und verwendet es, um zu überprüfen, dass der Konstruktor mit den richtigen Parametern umgehen können, oder überschüssige Parameter fehlen, dass die Accessoren das Zeug zurückzukehren, und dass message und stringify beide schaffen den richtigen Inhalt.

Denken Sie daran, dass diese Tests hauptsächlich ein Sicherheitsnetz, wenn eine Änderung des Codes (vor allem nach ein paar Monaten). Wenn Sie versehentlich etwas an der falschen Stelle ändern, werden die Tests fehlschlagen.

package main; # something like 01_foo.t 
use strict; 
use warnings; 
use Test::More; 
use Test::Exception; 
use LWP::Simple 'head'; 

subtest 'Functionality of Error' => sub { 
    my $example_error = { 
     category => 'Connection Error', 
     template => 'Could not ping switch %s in %s seconds.', 
     context => [qw(switch_ip timeout)], 
     fatal  => 1, 
     wiki_page => 'http://example.org', 
    }; 

    # happy case 
    { 
     my $e = Error->new(
      $example_error, 
      timeout => 30, 
      switch_ip => '127.0.0.1' 
     ); 
     isa_ok $e, 'Error'; 

     can_ok $e, 'category'; 
     is $e->category, 'Connection Error', 
      q{... and it returns the correct value}; 

     can_ok $e, 'template'; 
     is $e->template, 'Could not ping switch %s in %s seconds.', 
      q{... and it returns the correct values}; 

     can_ok $e, 'context'; 
     is_deeply $e->context, [ 'switch_ip', 'timeout' ], 
      q{... and it returns the correct values}; 

     can_ok $e, 'is_fatal'; 
     ok $e->is_fatal, q{... and it returns the correct values}; 

     can_ok $e, 'message'; 
     is $e->message, 'Could not ping switch 127.0.0.1 in 30 seconds.', 
      q{... and the message is correct}; 

     can_ok $e, 'stringify'; 
     is $e->stringify, 
      "Connection Error : Could not ping switch 127.0.0.1 in 30 seconds.\n" 
      . "More info: http://example.org", 
      q{... and stringify contains the right message}; 
    } 

    # not enough arguments 
    throws_ok(sub { Error->new($example_error, timeout => 1) }, 
     qr/switch_ip/, q{Creating without switch_ip dies}); 

    # too many arguments 
    lives_ok(
     sub { 
      Error->new(
       $example_error, 
       timeout => 1, 
       switch_ip => 2, 
       foo  => 3 
      ); 
     }, 
     q{Creating with too many arguments lives} 
    ); 

}; 

Es fehlen einige spezifische Testfälle. Wenn Sie ein metrisches Tool wie Devel::Cover verwenden, ist es erwähnenswert, dass eine vollständige Abdeckung nicht bedeutet, dass alle möglichen Fälle abgedeckt sind.

Tests für Ihre Fehlerdatenqualität

nun den zweiten Teil, der in diesem Beispiel wert ist bedeckt, ist die Richtigkeit der Fehlervorlagen in ErrorLibrary. Jemand könnte versehentlich später etwas verwechseln, oder es könnte ein neuer Platzhalter zu einer Nachricht hinzugefügt werden, aber nicht zu dem Kontext-Array.

Der folgende Testcode wird idealerweise in eine eigene Datei eingefügt und nur ausgeführt, wenn Sie mit der Bearbeitung eines Features fertig sind. Zur Veranschaulichung wird dies jedoch nur nach dem obigen Codeblock fortgesetzt, daher die beiden ersten Ebenen subtest s .

Der Hauptteil Ihrer Frage war über die Liste der Testfälle. Ich halte das für sehr wichtig. Sie möchten, dass Ihr Testcode sauber, leicht zu lesen und noch einfacher zu warten ist. Der Test ist häufig eine Dokumentation, und nichts ist ärgerlicher, als den Code zu ändern und dann herauszufinden, wie die Tests funktionieren, damit Sie sie aktualisieren können. Also immer daran denken:

Tests sind auch Produktionscode!

Schauen wir uns nun die Tests für die Fehler an.

subtest 'Correctness of ErrorList' => sub { 

    # these test cases contain all the errors from ErrorList 
    my @test_cases = (
     { 
      name => 'ERROR_WIFI_CABLE_TOO_SHORT', 
      args => { 
       length => 2, 
      }, 
      message => 'A WiFi cable of 2 meters is too short.', 
     }, 
     { 
      name => 'ERROR_CABLE_HAS_WRONG_COLOR', 
      args => { 
       router => 'foo', 
       color => 'red', 
      }, 
      message => 'You cannot connect to foo using a red cable.', 
     }, 
     { 
      name => 'ERROR_I_AM_A_TEAPOT', 
      args => { 
       ip => '127.0.0.1', 
      }, 
      message => 'The device at 127.0.0.1 is a teapot.', 
     }, 
    ); 

    # use_ok 'ErrorList'; # only use this line if you have files! 
    ErrorList->import; # because we don't have a file ErrorList.pm 
          # in the file system 
    pass 'ErrorList used correctly'; # remove if you have files 

    foreach my $t (@test_cases) { 
     subtest $t->{name} => sub { 

      # because we need to use a variable to get to a constant 
      no strict 'refs'; 

      # create the Error object from the test data 
      # will also fail if the name was not exported by ErrorList 
      my $e; 
      lives_ok(
       sub { $e = Error->new(&{ $t->{name} }, %{ $t->{args} }) }, 
       q{Error can be created}); 

      # and see if it has the right values 
      is $e->message, $t->{message}, 
       q{... and the error message is correct}; 

      # use LWP::Simple to check if the wiki page link is not broken 
      ok head($e->wiki_page), q{... and the wiki page is reachable}; 
     }; 
    } 
}; 

done_testing; 

Es hat im Grunde eine Reihe von Testfällen, mit einem Fall für jede der möglichen Fehlerkonstanten, die von ErrorLibrary exportieren bekommen. Es hat den Namen, der verwendet wird, um den richtigen Fehler zu laden und den Testfall in der TAP-Ausgabe zu identifizieren, die erforderlichen Argumente zum Ausführen des Tests und die erwartete endgültige Ausgabe. Ich habe nur Nachricht aufgenommen, um es kurz zu halten.

Wenn ein Fehlervorlagenname in ErrorLibrary geändert (oder entfernt) wird, ohne den Text zu ändern, schlägt die lives_ok Umgebung der Objekt Instanziierung fehl, weil dieser Name nicht exportiert wurde. Das ist ein schönes Plus.

Es wird jedoch nicht fangen, wenn ein neuer Fehler ohne einen Testfall hinzugefügt wurde. Ein Ansatz dafür wäre, die Symboltabelle im main-Namespace zu betrachten, aber das ist für den Umfang dieser Antwort ein wenig zu weit fortgeschritten.

Was es auch tut, ist LWP::Simple verwenden, um eine HEAD HTTP-Anfrage an jede Wiki-URL zu tun, um zu sehen, ob diese erreichbar sind. Das hat auch den Vorteil, dass es beim Ausführen eines Builds ein wenig wie ein Monitoring-Tool funktioniert.

es bringt alle zusammen

Schließlich ist hier der TAP-Ausgang, wenn sie ohne prove lief.

# Subtest: Functionality of Error 
    ok 1 - An object of class 'Error' isa 'Error' 
    ok 2 - Error->can('category') 
    ok 3 - ... and it returns the correct value 
    ok 4 - Error->can('template') 
    ok 5 - ... and it returns the correct values 
    ok 6 - Error->can('context') 
    ok 7 - ... and it returns the correct values 
    ok 8 - Error->can('is_fatal') 
    ok 9 - ... and it returns the correct values 
    ok 10 - Error->can('message') 
    ok 11 - ... and the message is correct 
    ok 12 - Error->can('stringify') 
    ok 13 - ... and stringify contains the right message 
    ok 14 - Creating without switch_ip dies 
    ok 15 - Creating with too many arguments lives 
    1..15 
ok 1 - Functionality of Error 
    # Subtest: Correctness of ErrorList 
    ok 1 - ErrorList used correctly 
     # Subtest: ERROR_WIFI_CABLE_TOO_SHORT 
     ok 1 - Error can be created 
     ok 2 - ... and the error message is correct 
     ok 3 - ... and the wiki page is reachable 
     1..3 
    ok 2 - ERROR_WIFI_CABLE_TOO_SHORT 
     # Subtest: ERROR_CABLE_HAS_WRONG_COLOR 
     ok 1 - Error can be created 
     ok 2 - ... and the error message is correct 
     ok 3 - ... and the wiki page is reachable 
     1..3 
    ok 3 - ERROR_CABLE_HAS_WRONG_COLOR 
     # Subtest: ERROR_I_AM_A_TEAPOT 
     ok 1 - Error can be created 
     ok 2 - ... and the error message is correct 
     ok 3 - ... and the wiki page is reachable 
     1..3 
    ok 4 - ERROR_I_AM_A_TEAPOT 
    1..4 
ok 2 - Correctness of ErrorList 
1..2 
+0

konnte öffnen wir eine Diskussion, wenn Sie frei sind? ich auf dem Code ein paar Fragen. Mein funtionality Test funktioniert gut, aber meine Korrektheit Test gibt. –

+1

@PaulRussell das alte gehen in – simbabque

+0

Ich werde die meiste Zeit für die nächsten Tage online sein. Vielen Dank –

Verwandte Themen