Wie teste ich meine Module?
Inhalt:
Testskripte für Module
Wie man ein CPAN-konformes Modul erstellt, wurde schon im anderen Artikel
erklärt. Dort habe ich auch erwähnt, dass man seine Module ausreichend umfassend
testen soll. Dafür sind die Testskripte gedacht, die mit jeder Distribution
ausgeliefert werden.
Es gibt Strategien, die besagen, dass man ein Modul anhand von Tests entwickeln
sollte. Das heißt, dass man erst das Testskript schreibt und dann das Modul immer
weiter entwickelt.
Wir benutzen jetzt mal das Modul Test::More für die Tests.
Wenn der Modulrumpf mit h2xs erstellt wurde, sieht das Testskript so aus:
# Before `make install' is performed this script should be runnable with
# `make test'. After `make install' it should work as `perl Modultests.t'
#########################
# change 'tests => 1' to 'tests => last_test_to_print';
use Test::More tests => 1;
BEGIN { use_ok('PerlCommunity::Testmodul') };
#########################
# Insert your test code below, the Test::More module is use()ed here so read
# its man page ( perldoc Test::More ) for help writing this test script.
Am Anfang muss man dem Modul Test::More mitteilen, wie viele Tests durchlaufen
werden sollen. Zu Beginn ist es nur ein Test.
In dem BEGIN-Block wird schonmal überprüft, ob das neue Modul überhaupt geladen
werden kann.
Sein oder nicht Sein...
Danach kann man ganz normalen Perl-Code einfügen und zum Testen wird dann meistens
die Methode ok (siehe perldoc Test::More) benutzt. Da unser neues Modul ein
Objektorientiertes Modul ist, erzeugen wir ein Objekt und überprüfen, ob es zu
der gewünschten Klasse gehört:
my $object = PerlCommunity::Testmodul->new();
isa_ok($object,'PerlCommunity::Testmodul');
Was kann es?
Als nächstes prüfen wir, ob alle gewünschten Methoden implementiert sind. D.h. ob
das Objekt diese Methoden ausführen kann:
my @method_names = qw(echo echo_greeting farbe);
can_ok($object,@method_names);
Wenn man die Methoden implementiert hat, sollte der Test jetzt erfolgreich durchlaufen.
Beginnt man aber von der anderen Seite ('Tests first'), dann wird der Test fehlschlagen:
~/EigeneModule/PerlCommunity-Testmodul 156> make test
cp lib/PerlCommunity/Testmodul.pm blib/lib/PerlCommunity/Testmodul.pm
/usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/PerlCommunity-Testmodul....
# Failed test 'PerlCommunity::Testmodul->can(...)'
t/PerlCommunity-Testmodul....ok 1/2# in t/PerlCommunity-Testmodul.t at line 20.
# PerlCommunity::Testmodul->can('echo') failed
# PerlCommunity::Testmodul->can('echo_greetings') failed
# PerlCommunity::Testmodul->can('farbe') failed
t/PerlCommunity-Testmodul....NOK 2# Looks like you failed 1 test of 2.
t/PerlCommunity-Testmodul....dubious
Test returned status 1 (wstat 256, 0x100)
DIED. FAILED test 2
Failed 1/2 tests, 50.00% okay
Failed Test Stat Wstat Total Fail Failed List of Failed
-------------------------------------------------------------------------------
t/PerlCommunity-Testmodul.t 1 256 2 1 50.00% 2
Failed 1/1 test scripts, 0.00% okay. 1/2 subtests failed, 50.00% okay.
make: *** [test_dynamic] Error 2
Hier erkennt man dann gleich, welche Methoden noch nicht implementiert wurden.
Woher die Daten?
Beim Testen sollte man nicht den Fehler machen, "errechnete" Daten zum Testen benutzen. Das
soll heißen, dass man nicht ein Programm schreiben sollte, das das Modul benutzt und die Ausgaben
in dann in die Tests zu integrieren. Man sollte von der Spezifikation ausgehen und die erwarteten
Ergebnisse in die Tests übernehmen.
Nehmen wir mal an, die Spezifikation sagt, dass die Methode 'echo_greetings' "Hello,
!" zurückliefern
sollte. Der Programmierer hat aber ein "Hallo, !" geschrieben:
sub echo_greetings{
my ($self,$adressat) = @_;
return 'Hallo, '.$adressat.'!';
}
Wenn ich jetzt ein Programm schreibe, das mein Modul benutzt und dann den Rückgabewert in die Tests nehme,
teste ich ja schon auf das falsche. Vergleiche (Annahme: alle Methoden wurden schon implementiert):
# Aus dem Programm heraus genommen
# *NICHT* (!) der Spezifikation entsprechend
ok($object->echo_greetings("Welt") eq "Hallo, Welt!");
~/EigeneModule/PerlCommunity-Testmodul 161> make test
cp lib/PerlCommunity/Testmodul.pm blib/lib/PerlCommunity/Testmodul.pm
/usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/PerlCommunity-Testmodul....ok
All tests successful.
Files=1, Tests=2, 0 wallclock secs ( 0.05 cusr + 0.01 csys = 0.06 CPU)
und
# der Spezifikation entsprechend
ok($object->echo_greetings("Welt") eq "Hello, Welt!");
~/EigeneModule/PerlCommunity-Testmodul 162> make test
/usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/PerlCommunity-Testmodul....
t/PerlCommunity-Testmodul....ok 1/2# Failed test in t/PerlCommunity-Testmodul.t at line 21.
t/PerlCommunity-Testmodul....NOK 2# Looks like you failed 1 test of 2.
t/PerlCommunity-Testmodul....dubious
Test returned status 1 (wstat 256, 0x100)
DIED. FAILED test 2
Failed 1/2 tests, 50.00% okay
Failed Test Stat Wstat Total Fail Failed List of Failed
-------------------------------------------------------------------------------
t/PerlCommunity-Testmodul.t 1 256 2 1 50.00% 2
Failed 1/1 test scripts, 0.00% okay. 1/2 subtests failed, 50.00% okay.
make: *** [test_dynamic] Error 2
Hier erkennt der Programmierer gleich, dass etwas nicht stimmt.
Besonders tückisch wird es, wenn man Berechnungen anstellt - wie das Wurzelziehen. Da es durch die
Darstellung von Gleitkommazahlen zu Problemen kommen könnte, stellt man solche Tests etwas anders an:
# im Modul:
sub wurzel{
my ($self,$value) = @_;
return sqrt($value);
}
# im Testskript
for(1..10){
ok($object->wurzel($_ * $_) == $_);
}
Hier kommt es zu keinen Darstellungsproblemen...
SKIP und TODO
... folgt in Kürze...
Das sieht jetzt alles so aus, als wäre es nur für irgendwelche hochtrabenden Projekte geeignet, aber ich
kann aus eigener Erfahrung sagen, dass es sich auch für einfach Module lohnt, die eigentlich nur für den
"Hausgebrauch" geschrieben werden.
-- ReneeBaecker - 27 Mar 2006