Seitenanfang

TDD - Test driven development

Dieser Post wurde aus meiner alten WordPress-Installation importiert. Sollte es Darstellungsprobleme, falsche Links oder fehlende Bilder geben, bitte einfach hier einen Kommentar hinterlassen. Danke.


Nachdem MongoDB das Datenbankrennen knapp gewann, stand heute eine passende Abstraktionsschicht für den Zugriff auf dem Plan.

Bei diesem Schritt habe ich mich für "Test driven development" - kurz TDD - entschieden, einem Verfahren bei dem erst das Endergebnis in mittels einer Reihe von Tests definiert wird und dann ein Modul (bzw. Programm) entwickelt wird, dass alle Tests erfolgreich besteht. Wurden die Tests entsprechend sauber und vollständig geschrieben, ist das Projekt dann abgeschlossen.

Zunächst musste ich feststellen, dass sich im erwähnten Post über das Finale des Datenbank-Rennens ein Fehler eingeschlichen hat: Das neue Modul sollte eine Schnittstelle haben, die zu einem auf DBIx::Class aufsetzenden YAWF::Object kompatibel ist. YAWF ist mein - bisher noch nicht veröffentlichtes - Web-Framework und YAWF::Object vereinfacht die DBIx::Class-Nutzung.

Für das neue Modul habe ich zunächst einen Test erstellt, der alle typischen Anwendungsfälle abdeckt:

  • Zwei MongoDB - Dokumente werden erstellt
  • Beide werden über ihre ID geladen
  • Beide werden über alle definierten Felder geladen, da kein Wert doppelt vorkommt
  • Ein dritter Eintrag wird erstellt, der sich in einem Feld mit dem ersten überschneidet
  • Der dritte Eintrag enthält ein vorher nicht definiertes Feld
  • Der dritte Eintrag wird über das neue Feld geladen
  • Die Liste und Anzahl der Dokumente werden von der Datenbank abgefragt
  • Eintrag 1 und 3 werden über ihr gemeinsames Feld geladen und gelöscht
  • Die Anzahl der Dokumente in der Datenbank wird erneut überprüft, um den Löschvorgang zu verifizieren
  • Eintrag 2 wird gelöscht und die Löschung verifiziert
Hier ist das passende Testscript dazu:
 use Test::More tests => 34;

use_ok('YAWF::Object::MongoDB');use_ok('t::lib::Car');

my $first = t::lib::Car->new; # Create a new caris( ref($first), 't::lib::Car', 'Check first object type' );is( $first->color('red'), 'red', 'Set first color' );is( $first->brand('Ferrari'), 'Ferrari', 'Set first brand' );is( $first->model('Enzo'), 'Enzo', 'Set first model' );ok( $first->flush, 'Flush first object to database' );

my $second = t::lib::Car->new; # Create a new caris( ref($second), 't::lib::Car', 'Check second object type' );is( $second->color('blue'), 'blue', 'Set second color' );is( $second->brand('Jaguar'), 'Jaguar', 'Set second brand' );is( $second->model('XF'), 'XF', 'Set second model' );ok( $second->flush, 'Flush second object to database' );

is( t::lib::Car->new( $first->id )->id, $first->id, 'Get first document by id' );for ( 'color', 'brand', 'model' ) { is( t::lib::Car->new( $_ => $first->$_ )->id, $first->id, 'Get first document by ' . $_ );}

is( t::lib::Car->new( $second->id )->id, $second->id, 'Get second document by id' );for ( 'color', 'brand', 'model' ) { is( t::lib::Car->new( $_ => $second->$_ )->id, $second->id, 'Get second document by ' . $_ );}

my $third = t::lib::Car->new; # Create a new caris( $third->color('red'), 'red', 'Set third color' );is( $third->set_column( 'engine', '12V' ), '12V', 'Set third custom key' );ok( $third->flush, 'Flush third object to database' );is( t::lib::Car->new( engine => $third->get_column('engine') )->id, $third->id, 'Get third document by custom key' );

my @list = t::lib::Car->list;is( scalar(@list), 3, 'Check car list' );is( t::lib::Car->count, 3, 'Check car count' );

@list = t::lib::Car->list( { color => 'red' } );is( scalar(@list), 2, 'Check red car count' );

for (@list) { ok( $_->delete, 'Remove a car' );}

my @list = t::lib::Car->list;is( scalar(@list), 1, 'Check car list' );is( t::lib::Car->count, 1, 'Check car count' );

for (@list) { ok( $_->delete, 'Remove last car' );}

my @list = t::lib::Car->list;is( scalar(@list), 0, 'Check car list' );is( t::lib::Car->count, 0, 'Check car count' );

END { t::lib::Car->_collection->drop;}

Sicherlich werden nicht alle denkbaren Fälle abgedeckt, aber die üblichen Anwendungen sind mit diesem Test abgedeckt.

Im Rahmen der Entwicklung entstanden zwei weitere Test-Skripte für das neue Modul: Eines zum Debuggen der internen Strukturen: Es vergleicht die von den veröffentlichten Methoden zurückgegeben Werte mit denen der internen Strukturen im Objekt und ein zweites Script, dass nur die Konvertierung eines DBIx::Class::ResultSet (bzw. SQL::Abstract) kompatiblen order_by Parameters in MongoDB-Syntax durchführt.

Während der zweite Test zur Fehlersuche geschrieben wurde, entstand das dritte Test-Script, als ich in die Verlegenheit kam, das order_by-Attribut richtig auszuwerten - und zwar wieder bevor der passende Sourcecode geschrieben wurde.

Zwischendurch lief immer wieder mal das oben gezeigte Testscript und gab den aktuellen Stand der Entwicklung wieder: Schlugen beim ersten Versuch noch alle Tests fehl, änderte sich das Ergebnis nach und nach, bis schließlich alle Einzeltests mit "ok" abgeschlossen wurden.

TDD hat zwei wesentliche Vorteile:

  1. Beim Schreiben des Tests sind die Chancen, eine Funktion zu vergessen, geringer als bei der Entwicklung des eigentlichen Moduls/Scripts.
  2. Im Gegensatz zu manuellen Tests können Test-Scripte auch komplizierte Situationen (wie in diesem Fall, in dem mit einem auf das eigentlich zu testende Modul aufsetzenden Objektmodul gearbeitet werden muss) sehr einfach abbilden und die Tests können zu jeder Zeit wiederholt werden und decken bei zukünftigen Änderungen mögliche Fehler auf.
 
 

Noch keine Kommentare. Schreib was dazu

Schreib was dazu

Die folgenden HTML-Tags sind erlaubt:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>