Development

Documentation/de_DE/book/1.0/15-Unit-and-Functional-Testing

You must first sign up to be able to contribute.

Version 2 (modified by Jan.Kunzmann, 10 years ago)
15%

ARBEITSENTWURF ROHÜBERSETZUNG / TRANSLATION WORKING DRAFT
Originaldokument: http://svn.symfony-project.com/doc/branches/1.1/book/15-Unit-and-Functional-Testing.txt, Version 7705 vom 2008-03-01
Übersetzungsfortschritt: 15%
Übersetzung: JK
Korrekturfortschritt: 0%

Kapitel 15 - Unit- und Funktionalitätstests

Automatisierte Tests stellen einen der größsten Fortschritte in der Programmierung seit der Objektorientierung dar. Besonders hilfreich sind sie bei der Entwicklung von Webanwendungen, denn sie garantieren die Qualität einer Applikation selbst dann, wenn oft released wird. Symfony stellt eine ganze Reihe an Werkzeugen für das automatisierte Testen bereit, die in diesem Kapitel vorgestellt werden.

Automatisierte Tests

Jeder erfahrene Entwickler weiß genau, welche Zeit es benötigt, um Webanwendungen gut zu testen. Es ist lästig, Testcases zu schreiben, sie laufen zu lassen und die Ergebnisse zu analyiseren. Zusätzlich ändern sich die Anforderungen an Webanwendungen meist stetig, was wiederum zu einem ununterbrochenen Strom von Releases führt und ein fortwährendes Refactoring des Codes notwendig macht. In diesem Umfeld ist es sehr wahrscheinlich, dass regelmäßig neue Fehler auftauchen.

Aus diesem Grund werden automatisierte Tests als Teil einer sinnvollen Entwicklungsumgebung gesehen, auch wenn sie nicht unbedingt notwendig sind. Ein paar Testcases kann sicherstellen, dass eine Anwendung das tut, was man erwartet. Selbst wenn die Internas oft überarbeitet werden, verhindern automatisierte Tests versehentliche Rückschritte. Außerdem zwingen sie Entwickler dazu, Tests in einem standardisierten, starren Format zu schreiben, das von einem Test-Framework verstanden werden kann.

Automatisierte Tests können gelegentlich auch die Entwicklerdokumentation ersetzen, da sie deutlich machen, was die Anwendung eigentlich tun soll. Eine gute Testsuite zeigt an, welche Ausgaben von einer Testeingabe erwartet werden, was wiederum eine gute Möglichkeit ist, den Zweck einer Methode zu erklären.

Das Symfony-Framework wendet dieses Prinzip auch auf sich selbst an. Die Internas des Frameworks werden durch automatisierte Tests überprüft. Diese Unit- und Funktionalitäts-Tests werden nicht zusammen mit der normalen Symfony-Distribution ausgeliefert, aber Sie können sie aus dem SVN-Repository auschecken oder utner http://trac.symfony-project.com/browser/trunk/test durchstöbern.

Unit-Test und Funktionalitäts-Tests

Unit-Tests bestätigen, dass eine abgeschlossene Code-Komponente die korrekte Ausgabe für eine bestimmte Eingabe liefert. Sie überprüfen, wie Funktionen und Methoden in allen Einzelfällen arbeiten. Unit-Tests behandeln stets nur einen Test auf einmal, und somit könnte eine einzige Methode mehrere Unit-Tests benötigen, wenn sie in bestimmten Situationen unterschiedlich arbeitet.

Funktionalitäts-Tests überprüfen nicht nur eine einfache Umsetzung von Eingabe zu Ausgabe, sondern ein komplettes Feature. Ein Cache-System beispielsweise kann nur durch einen Funktionalitätstest überprüft werden, da hierfür mehr als ein einzelner Schritt notwendig ist: Wird die Seite das erste mal aufgerufen, wird sie ausgeführt; beim zweiten Mal wird sie aus dem Cache geholt. Funktionalitäts-Tests überprüfen also einen Prozess und brauchen ein Szenario. In Symfony sollten Sie Funktionalitäts-Tests für all Ihre Actions schreiben.

Für hochkomplexe Interaktionen können sich die beiden Testarten überschneiden. Ajax-Interaktionen benötigen beispielsweise einen Webbrowser, um JavaScript auszuführen, weshalb automatische Tests ein entsprechendes Tool eines Drittanbieters benötigen. Und visuelle Effekte können überhaupt nur von einem Menschen überprüft werden.

Wenn Sie automatisierte Tests intensiv nutzen wollen, müssen Sie wahrscheinlich eine Kombination all dieser Methoden verwenden. Als Regel sollten Sie sich merken, die Tests einfach und lesbar zu halten.

NOTE Automatisierte Tests vergleichen ein Ergebnis mit der erwarteten Ausgabe. Mit anderen Worten werden Behauptungen (engl. "Assertion") ausgewertet (Ausdrücke wie $a == 2). Das Ergebnis einer Behauptung ist entweder wahr (true) oder falsch (false), wovon abhängt, ob ein Test erfolgreich war oder fehlgeschlagen ist. Das Wort "Assertion" wird häufig verwendet, wenn es um Techniken für automatisierte Tests geht.

Testgestützte Entwicklung

Bei der Testgestützten Entwicklung (engl. Test-Driven Development, TDD) werden die Tests noch vor dem eigentlichen Code geschrieben. Indem Sie die Tests zuerst schreiben, können Sie sich noch vor dem Entwickeln einer Funktion darauf konzentrieren, welche Aufgaben sie erledigen soll. Es handelt sich um eine gute Technik, die auch von anderen Entwicklungsmethoden wie Extreme Programming (XP) empfohlen werden. Außerdem umschifft Sie mit dieser Methode ein nicht zu leugnendes Problem: Wenn Sie Ihre Unit-Tests nicht sofort schreiben, schreiben Sie sie nie.

Stellen Sie sich vor, Sie müssen eine Funktion zum Beschneiden von Text schreiben. Die Funktion entfernt Leerzeichen an Anfang und Ende des Texts, ersetzt Sonderzeichen durch Unterstriche und wandelt alle Großbuchstaben in Kleinbuchstaben um. Bei der Testgestützten Entwicklung würden Sie zunächst über alle möglichen Fälle nachdenken, und danach für jeden Fall Beispieleingaben und die erwarteten Ausgaben zusammenstellen, wie in Tabelle 15-1 gezeigt.

Tabelle 15-1 - Eine Liste mit Test-Cases für eine Funktion zum Beschneiden von Text

Eingabe | Erwartete Ausgabe ----------------------- | ----------------------- " foo " | "foo" "foo bar" | "foo_bar" "-)foo:..=bar?" | "__foo____bar_" "FooBar" | "foobar" "Foo-bar mich nicht!" | "foo_bar_mich_nicht_"

Sie würden die Unit-Tests schreiben, sie ausführen und feststellen, dass sie fehlschlagen. Dann würden Sie Code hinzufügen, der den ersten Testcase abhandelt, die Tests erneut ausführen, feststellen, dass nun der erste erfolgreich ist, usw. Wenn schließlich alle Testcases erfolgreich sind, arbeitet die Funktion korrekt.

Eine Anwendung, die mit TDD erstellt wurde, hat am Ende in etwa so viel Testcode wie tatsächlichen Code. Weil Sie für das Debuggen Ihrer Testcases keine Zeit verschwenden wollen, sollten Sie sie einfach halten.

NOTE Das Refactoring (dt. "Umgestaltung") einer Methode kann neue Fehler einführen, die vorher so nicht aufgetaucht sind. Aus diesem Grund ist es üblich, alle automatisierten Tests laufen zu laseen, bevor ein neues Release einer Anwendung in den Produktivbetrieb entlassen wird - dies wird auch als Regressionstest (engl. Regression Testing) bezeichnet.

Das "Lime"-Testframework

In der PHP-Welt gibt es einige Frameworks für Unit-Tests, z.B. PhpUnit oder SimpleTest. Symfony besitzt sein eigenes namens lime. Es basiert auf der Perl-Bibliothek Test::More und ist TAP-konform, was bedeutet, dass die Ausgaben gemäß des Test Anything Protocols angezeigt werden, welches für bessere Lesbarkeit von Testausgaben gestaltet wurde.

Lime unterstützt Sie bei Unit-Tests. Es ist einfacher als andere PHP-Testframeworks und mehrere Vorteile:

  • Es started die Testdateien in einer abgeschlossenen Umgebung ("Sandbox"), um seltsame Seiteeffekte zwischen den einzelnen Testläufen zu vermeiden. Nicht alle Test-Frameworks garantieren eine saubere Umgebung für jeden Testlauf.
  • Lime-Tests sind äußerst leserlich, genauso wie ihre Ausgabe. Bei entsprechenden Systemvoraussetzungen verwendet Lime farbige Ausgabe, um wichtige Informationen hervorzuheben.
  • Symfony selbst verwendet Lime-Tests als Regressionstests, und somit finden Sie viele Beispiele für Unit- und Funktionalitätstests im Sourcecode von Symfony.
  • Lime selbst wird ebenfalls durch Unit-Tests überprüft.
  • Es ist komplett in PHP geschrieben, sauber implementiert und schnell. Und es besteht nur aus einer einzigen Datei, lime.php, ohne weitere Abhängigkeiten.

Die diversene Tests in den nächsten Abschnitten verwenden die Syntax von Lime. Sie funktionieren ohne weitere Anpassung in jeder Symfony-Installation.

NOTE Unit- und Funktionalitätstests sind nicht dazu gedacht, in einer Produktivumgebung ausgeführt zu werden. Sie sind Entwicklungswerkzeuge und sollten als solche auch nur auf dem Computer des Entwicklers laufen, nicht auf dem Server.

Unit-Tests

Symfonys Unit-Tests sind einfache PHP-Dateien, die auf Test.php enden und im Verzeichnis test/unit/ innerhalb Ihres Projekts liegen müssen. Ihre Syntax ist einfach und lesbar.

Wie sehen Unit-Tests aus?

Listing 15-1 zeigt eine typische Abfolge eines Unit-Tests für die Funktion strtolower(). Es beginnt mit der Instantiierung des lime_test-Objekts (im Augenblick müssen Sie sich noch keine Gedanken über die Parameter machen). Jeder Unit-Test besteht aus einem Aufruf einer Methode der Instanz von lime_test. Der letzte Parameter dieser Methoden ist stets ein optionaler Zeichenkette, der als Ausgabe dient.

Listing 15-1 - Beispieldatei für einen Unit-Test, in test/unit/strtolowerTest.php

[php]
<?php

include(dirname(__FILE__).'/../bootstrap/unit.php');
require_once(dirname(__FILE__).'/../../lib/strtolower.php');

$t = new lime_test(7, new lime_output_color());

// strtolower()
$t->diag('strtolower()');
$t->isa_ok(strtolower('Foo'), 'string',
    'strtolower() gibt einen String zurück');
$t->is(strtolower('FOO'), 'foo',
    'strtolower() formt die Eingabe in Kleinbuchstaben um');
$t->is(strtolower('foo'), 'foo',
    'strtolower() lässt Kleinbuchstaben unverändert');
$t->is(strtolower('12#?@~'), '12#?@~',
    'strtolower() lässt Sonderzeichen unverändert');
$t->is(strtolower('FOO BAR'), 'foo bar',
    'strtolower() lässt Leerzeichen bestehen');
$t->is(strtolower('FoO bAr'), 'foo bar',
    'strtolower() kommt mit gemischter Groß- und Kleinschreibung zurecht');
$t->is(strtolower(''), 'Nichts',
    'strtolower() formt Leerstrings zu Nichts um');

Führen Sie den Test von der Kommandozeile über den test-unit-Task aus. Die Ausgabe auf der Kommandozeile ist sehr ausführlich und hilft Ihnen, zu ermitteln, welche Tests fehlgeschlagen sind und welche erfolgreich waren. Die Ausgabe der Beispieltests sehen Sie in Listing 15-2.

Listing 15-2 - Starten eines einzelnen Unit-Tests von der Kommandozeile

> symfony test-unit strtolower

1..7
# strtolower()
ok 1 - strtolower() gibt einen String zurück
ok 2 - strtolower() formt die Eingabe in Kleinbuchstaben um
ok 3 - strtolower() lässt Kleinbuchstaben unverändert
ok 4 - strtolower() lässt Sonderzeichen unverändert
ok 5 - strtolower() lässt Leerzeichen bestehen
ok 6 - strtolower() kommt mit gemischter Groß- und Kleinschreibung zurecht
not ok 7 - strtolower() formt Leerstrings zu Nichts um
#     Failed test (.\batch\test.php at line 21)
#            got: ''
#       expected: 'foo'
# Looks like you failed 1 tests of 7.

TIPP Das include-Statement am Anfang von Listing 15-1 ist optional. Es sorgt allerdings dafür, dass die Testdatei ein unabhängiges PHP-Script wird, das Sie auch ohne das Kommandozeilenwerkzeug von Symfony starten können, indem Sie php test/unit/strtolowerTest.php aufrufen.

Methoden für Unit-Tests

Das lime_test-Objekt besitzt eine Reihe von Testmethoden, die in Tabelle 15-2 aufgeführt sind.

Tabelle 15-2 - Methoden des lime_test-Objekts für Unit-Tests

Methode | Beschreibung ------------------------------------------- | ------------------------------------------------------------------------------------------------ diag($msg) | Gibt einen Kommentar aus, ohne einen Test auszuführen ok($test, $msg) | Testet eine Bedingung und ist erfolgreich, wenn sie wahr ist is($value1, $value2, $msg) | Vergleicht zwei Werte und ist erfolgreich, wenn sie identisch sind (==) isnt($value1, $value2, $msg) | Vergleicht zwei Werte und ist erfolgreich, wenn diese verschieden sind like($string, $regexp, $msg) | Testst eine Zeichenkette gegen einen Regulären Ausdruck unlike($string, $regexp, $msg) | Prüft, ob eine Zeichenkette nicht auf einen Regulären Ausdruck passt cmp_ok($value1, $operator, $value2, $msg) | Vergleicht zwei Argumente mit einem bestimmten Operatoren isa_ok($variable, $type, $msg) | Prüft den Variablentyp des Arguments isa_ok($object, $class, $msg) | Prüft die Klasse eines Objekts can_ok($object, $method, $msg) | Prüft die Verfügbarkeit einer Methode in einem Objekt oder einer Klasse is_deeply($array1, $array2, $msg) | Prüft, ob zwei Arrays die gleichen Werte enthalten include_ok($file, $msg) | Prüft, ob eine Datei existiert und ob sie korrekt inkludiert ist fail() | Schlägt immer fehl - nützlich zum Testen von Exceptions pass() | Ist immer erfolgreich - nützlich zum Testen von Exceptions skip($msg, $nb_tests) | Überspringt eine Anzahl von $nb_tests Tests - nützlich bei Tests, die von Bedingungen abhängen todo() | Zählt als Test - nützlich für Tests, die noch nicht geschrieben wurden