End-To-End-Tests in PHP lassen sich sehr elegant mit Codeception durchführen. Angelehnt an Behaviour-Driven-Tests ist der Sourcecode der Tests sehr ausdrucksstark, einfach zu schreiben und sehr gut lesbar.
Installation
Codeception wird über Composer installiert. Das zu installierende Paket heißt
Codeception/Codeception
Nach der Installation steht uns im Ordner \vendor\bin\ eine Batch-Datei namens codecept.bat zur Verfügung, mit der wir die weitere Einrichtung und den Aufruf der Tests durchführen können.
Codeception einrichten
Das initiale Einrichten von Codeception wird bootstrap genannt. Um den Bootstrap von Codeception durchzuführen muss die oben angegebene Batch-Datei mit folgenden Parametern aufgerufen werden:
codecept bootstrap
Dieser Aufruf erstellt die Beschreibungsdatei Codeception.yml und ein Verezichnis tests mit Unterordnern.
Hinweis: Ich habe es nicht sauber hinbekommen, den Ordner in das Projekt zu schieben. Da mit Codeception aber auch Integrationstests verschiedener Projekte möglich ist, ist es vielleicht auch besser so, den Ordner auf oberster Ebene zu lassen.
Beim Bootstrap werden drei Test-Suites erstellt:
- acceptance
- functional
- unit
Löschen nicht verwendeter Suites
Ich verwende für Unit-Tests phpunit, auch funktionale Tests habe ich selten. Entweder sind es echte Akzeptanz-Tests der Webseite oder Tests von Webservices. Deshalb lösche ich diese Suites, darin schreibe ich niemals Tests.
Zum Löschen einfach alle Verzeichnisse mit dem Namen der Suites löschen, die nicht verwendet werden und in allen anderen Verzeichnissen die PHP-Dateien löschen, die den Namen der Suite tragen (also Functional*). Dann noch die .yml-Datei mit dem Namen der Suite im tests-Ordner löschen und man ist die nicht verwendeten Dateien los.
Akzeptanztests erstellen
Als nächstes wollen wir unseren ersten Akzeptanztest innerhalb der oben angelegten Suite erstellen.
Um einen Akzeptanztest zu erstellen rufen wir wieder die Batch-Datei auf:
codecept generate:cept acceptance [name des tests]
Hinweis: Im Standard wird pro Datei genau ein Akzeptanztest ausgeführt. Möchten wir, wie wir es von UnitTests gewohnt sind, ähnliche Tests in einer Testklasse gruppieren, müssen wir einen cest erstellen (nicht wie oben einen cept). Der Aufruf dazu:
codecept generate:cest acceptance [name der testklasse]
Nach diesen Aufrufen finden wir nun im Ordner der Suite (hier: acceptance) eine neue Datei, die einen Rumpf für einen Test enthält.
In diesen Rumpf können wir nun unsere Tests schreiben. Hier mal ein einfaches Beispiel, in dem das Routing einer auf dem Slim-Framework basierten Anwendung geprüft wird:
1 2 3 4 5 6 7 8 9 10 11 |
<?php class RoutingCest { public function ensureRoutingToSchemaVersion(AcceptanceTester $I) { $I->wantTo('ensure that routing to schema version works'); $I->amOnPage('/schemaVersion'); $I->dontSee('Fatal error:'); $I->dontSee('404'); } } |
Der Test ist sprechend, oder? Im Falle eines Routing-Fehlers gibt Slim eine Fehlerseite aus, auf der der Text „Fatal error“ steht. Finden wir diesen Text nicht und landen auch nicht auf einer Seite mit einem 404, ist das Routing in Ordnung.
Möchten wir nun weitere Tests hinzufügen, können wir dies einfach durch den obigen Aufruf erledigen.
Testsuite für REST-Services hinzufügen
Möchten wir nun eine weitere Test-Suite hinzufügen, etwa um einen Rest-Service zu testen, geht das ebenfalls über einen Aufruf der Batch-Datei:
codecept generate:cest api [name der testklasse]
Wichtig: Nach diesem Aufruf müssen wir noch Support-Klassen generieren lassen, die bei unserem ersten Beispiel oben implizit durch den bootstrap Parameter mit erstellt wurden. Um das zu erreichen, müssen wir folgenden Aufruf hinterher machen:
codecept build
Dies erstellt uns die Klasse tests/_support/ApiTester.php und tests/_generated/ApiTesterActions.php, die wir für die Ausführung der Tests benötigen.
Nach der Erstellung müssen wir die Suite noch konfigurieren. Dazu öffnen wir die Datei api.suite.yml und ergänzen einige Informationen:
1 2 3 4 5 6 7 8 |
class_name: ApiTester modules: enabled: - \Helper\Api - REST: url: http://localhost/ depends: PhpBrowser part: Json |
In diesem Beispiel etwa habe ich die URL angepasst, das Format auf Json eingeschränkt (zusätzlich gültig ist noch XML) welches Codeception für die Tests verwenden soll.
Nun können wir unseren API-Test schreiben. Hier wieder ein einfaches Beispiel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php class ReadingSchemaVersionCest { // tests public function tryToTest(ApiTester $I) { $I->wantTo('check the schema version via API'); $I->haveHttpHeader('Content-Type', 'application/x-www-form-urlencoded'); $I->sendGET('/schemaVersion'); $I->seeResponseCodeIs(200); $I->seeResponseIsJson(); $I->seeResponseContains('"Version":"2013-09-01 00:00:00 "'); } } |
Auch dieser Test ist wider sehr schön lesbar. Wir geben das Format an, welches wir verwenden, rufen die URL ab, von der wir das JSON-Ergebnis haben möchten und prüfen dann ob wir …
- … einen HTTP-Status 200 (OK) erhalten
- … unsere Antwort auch wirklich JSON ist
- … in unserer Antwort ein gewisses Ergebnis enthalten ist
Tests aufrufen und Prüfen
Die Tests lassen sich nun ganz einfach wieder über die Batch-Datei aufrufen:
codecept run
Das Ergebnis wird dann gleich in der Konsole angezeigt, und sieht dann etwa so aus:
Acceptance Tests (11) ——————————————————————————————-
Ensure that routing to entities works (RoutingEntitiesCest::ensureRoutingtoEntities) Ok
Ensure that routing to entities by id works (RoutingEntitiesCest::ensureRoutingtoEntitiesById) Ok
Ensure that routing to entities by name works (RoutingEntitiesCest::ensureRoutingtoEntitiesByName) Ok
Ensure that routing to ingredients works (RoutingIngredientsCest::ensureRoutingToIngredients) Ok
Ensure that routing to recipes works (RoutingRecipesCest::ensureRoutingToRecipes) Ok
Ensure that routing to recipes by id works (RoutingRecipesCest::ensureRoutingToRecipesById) Ok
Ensure that routing to recipes by name works (RoutingRecipesCest::ensureRoutingToRecipesByName) Ok
Ensure that routing to recipes by name works (RoutingRecipesCest::ensureRoutingToRecipesByNameLike) Ok
Ensure that routing to recipes by name works (RoutingRecipesCest::ensureRoutingToRecipesByIngredientName) Ok
Ensure that routing to recipes by name works (RoutingRecipesCest::ensureRoutingToRecipesByIngredientNames) Ok
Ensure that routing to schema version works (RoutingCest::ensureRoutingToSchemaVersion) Ok
—————————————————————————————————————–
Am Ende der Zeile wird „ok“ oder „fail“ ausgegeben, je nach Erfolg des Tests. Anschließend werden die Test-Ergebnisse aufgelistet, sodass man die fehlgeschlagenen Tests analysieren kann. Detaillierte Informationen darüber, was der Test geliefert hat, werden im Ordner _output in Form von HTML-Dateien abgelegt.
Wenn wir nur Tests für eine bestimmte Suite aufrufen wollen, geben wir am Ende des Aufrufs einfach den Namen der Suite an:
codecept run acceptance
Dann werden nur die Tests dieser Suite aufgerufen.
Fazit
Mit Codeception lassen sich sehr leicht schön lesbare Tests schreiben.
Leider fehlt ein Plugin für IntelliJ, sodass der Aufruf und die Auswertung des Ergebnisses über die Kommandozeile erfolgen muss (oder dem Terminal-Fenster in IntelliJ).
Aber die Ausgabe der Testergebnisse machen die Analyse der Tests einfach und übersichtlich.
So macht Testen Spaß!