{"id":293,"date":"2015-07-25T13:19:23","date_gmt":"2015-07-25T11:19:23","guid":{"rendered":"http:\/\/staratnight.de\/blog\/?p=293"},"modified":"2018-12-02T16:39:17","modified_gmt":"2018-12-02T14:39:17","slug":"e2e-tests-in-php-mit-codeception","status":"publish","type":"post","link":"https:\/\/staratnight.de\/blog\/e2e-tests-in-php-mit-codeception\/","title":{"rendered":"E2E-Tests in PHP mit Codeception"},"content":{"rendered":"<p>End-To-End-Tests in PHP lassen sich sehr elegant mit <a href=\"http:\/\/codeception.com\/\">Codeception<\/a> durchf\u00fchren. Angelehnt an Behaviour-Driven-Tests ist der Sourcecode der Tests sehr ausdrucksstark, einfach zu schreiben und sehr gut lesbar.<\/p>\n<p>&nbsp;<\/p>\n<h1>Installation<\/h1>\n<p>Codeception wird \u00fcber <a href=\"http:\/\/staratnight.de\/blog\/composer-in-intellij-verwenden\/\" target=\"_blank\" rel=\"noopener\">Composer installiert<\/a>. Das zu installierende Paket hei\u00dft<\/p>\n<p style=\"padding-left: 30px;\"><em><strong>Codeception\/Codeception<\/strong><\/em><\/p>\n<p>Nach der Installation steht uns im Ordner <em> \\vendor\\bin\\ <\/em>eine Batch-Datei namens <em>codecept.bat<\/em> zur Verf\u00fcgung, mit der wir die weitere Einrichtung und den Aufruf der Tests durchf\u00fchren k\u00f6nnen.<\/p>\n<p><!--more--><\/p>\n<h1>Codeception einrichten<\/h1>\n<p>Das initiale Einrichten von Codeception wird <em>bootstrap <\/em>genannt. Um den Bootstrap von <em>Codeception<\/em> durchzuf\u00fchren muss die oben angegebene Batch-Datei mit folgenden Parametern aufgerufen werden:<\/p>\n<p style=\"padding-left: 30px;\"><strong><em>codecept bootstrap<\/em><\/strong><\/p>\n<p>Dieser Aufruf erstellt die Beschreibungsdatei <em>Codeception.yml<\/em> und ein Verezichnis <em>tests<\/em> mit Unterordnern.<\/p>\n<p><strong>Hinweis: <\/strong>Ich habe es nicht sauber hinbekommen, den Ordner in das Projekt zu schieben. Da mit Codeception aber auch Integrationstests verschiedener Projekte m\u00f6glich ist, ist es vielleicht auch besser so, den Ordner auf oberster Ebene zu lassen.<\/p>\n<p>&nbsp;<\/p>\n<p>Beim Bootstrap werden drei Test-Suites erstellt:<\/p>\n<ul>\n<li>acceptance<\/li>\n<li>functional<\/li>\n<li>unit<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h2>L\u00f6schen nicht verwendeter Suites<\/h2>\n<p>Ich verwende f\u00fcr Unit-Tests phpunit, auch funktionale Tests habe ich selten. Entweder sind es echte Akzeptanz-Tests der Webseite oder Tests von Webservices. Deshalb l\u00f6sche ich diese Suites, darin schreibe ich niemals Tests.<\/p>\n<p>Zum L\u00f6schen einfach alle Verzeichnisse mit dem Namen der Suites l\u00f6schen, die nicht verwendet werden und in allen anderen Verzeichnissen die PHP-Dateien l\u00f6schen, die den Namen der Suite tragen (also Functional*). Dann noch die .yml-Datei mit dem Namen der Suite im <em>tests<\/em>-Ordner l\u00f6schen und man ist die nicht verwendeten Dateien los.<\/p>\n<p>&nbsp;<\/p>\n<h1>Akzeptanztests erstellen<\/h1>\n<p>Als n\u00e4chstes wollen wir unseren ersten Akzeptanztest innerhalb der oben angelegten Suite erstellen.<\/p>\n<p>Um einen Akzeptanztest zu erstellen rufen wir wieder die Batch-Datei auf:<\/p>\n<p style=\"padding-left: 30px;\"><strong><em>codecept generate:cept acceptance [name des tests]<\/em><\/strong><\/p>\n<p><strong>Hinweis: <\/strong>Im Standard wird pro Datei genau ein Akzeptanztest ausgef\u00fchrt. M\u00f6chten wir, wie wir es von UnitTests gewohnt sind, \u00e4hnliche Tests in einer Testklasse gruppieren, m\u00fcssen wir einen <em>cest<\/em> erstellen (nicht wie oben einen <em>cept<\/em>). Der Aufruf dazu:<\/p>\n<p style=\"padding-left: 30px;\"><strong><em>codecept generate:<span style=\"text-decoration: underline;\">cest<\/span> acceptance [name der testklasse]<\/em><\/strong><\/p>\n<p>Nach diesen Aufrufen finden wir nun im Ordner der Suite (hier: <em>acceptance<\/em>) eine neue Datei, die einen Rumpf f\u00fcr einen Test enth\u00e4lt.<\/p>\n<p>In diesen Rumpf k\u00f6nnen wir nun unsere Tests schreiben. Hier mal ein einfaches Beispiel, in dem das Routing einer auf dem Slim-Framework basierten Anwendung gepr\u00fcft wird:<\/p>\n<pre class=\"lang:php decode:true\">&lt;?php\r\nclass RoutingCest\r\n{\r\n \tpublic function ensureRoutingToSchemaVersion(AcceptanceTester $I)\r\n\t{\r\n\t\t$I-&gt;wantTo('ensure that routing to schema version works');\r\n\t\t$I-&gt;amOnPage('\/schemaVersion');\r\n\t\t$I-&gt;dontSee('Fatal error:');\r\n\t\t$I-&gt;dontSee('404');\r\n\t}\r\n}<\/pre>\n<p>Der Test ist sprechend, oder? Im Falle eines Routing-Fehlers gibt Slim eine Fehlerseite aus, auf der der Text &#8222;Fatal error&#8220; steht. Finden wir diesen Text nicht und landen auch nicht auf einer Seite mit einem 404, ist das Routing in Ordnung.<\/p>\n<p>M\u00f6chten wir nun weitere Tests hinzuf\u00fcgen, k\u00f6nnen wir dies einfach durch den obigen Aufruf erledigen.<\/p>\n<p>&nbsp;<\/p>\n<h1>Testsuite f\u00fcr REST-Services hinzuf\u00fcgen<\/h1>\n<p>M\u00f6chten wir nun eine weitere Test-Suite hinzuf\u00fcgen, etwa um einen Rest-Service zu testen, geht das ebenfalls \u00fcber einen Aufruf der Batch-Datei:<\/p>\n<p style=\"padding-left: 30px;\"><strong><em>codecept generate:<span style=\"text-decoration: underline;\">cest<\/span> api [name der testklasse]<\/em><\/strong><\/p>\n<p><strong>Wichtig: <\/strong>Nach diesem Aufruf m\u00fcssen wir noch Support-Klassen generieren lassen, die bei unserem ersten Beispiel oben implizit durch den <em>bootstrap <\/em>Parameter mit erstellt wurden. Um das zu erreichen, m\u00fcssen wir folgenden Aufruf hinterher machen:<\/p>\n<p style=\"padding-left: 30px;\"><em><strong>codecept build<\/strong><\/em><\/p>\n<p>Dies erstellt uns die Klasse <em>tests\/_support\/ApiTester.php<\/em> und <em>tests\/_generated\/ApiTesterActions.php<\/em>, die wir f\u00fcr die Ausf\u00fchrung der Tests ben\u00f6tigen.<\/p>\n<p>Nach der Erstellung m\u00fcssen wir die Suite noch konfigurieren. Dazu \u00f6ffnen wir die Datei api.suite.yml und erg\u00e4nzen einige Informationen:<\/p>\n<pre class=\"lang:yaml decode:true\">class_name: ApiTester\r\nmodules:\r\n    enabled:\r\n        - \\Helper\\Api\r\n        - REST:\r\n            url: http:\/\/localhost\/\r\n            depends: PhpBrowser\r\n            part: Json\r\n<\/pre>\n<p>In diesem Beispiel etwa habe ich die URL angepasst, das Format auf Json eingeschr\u00e4nkt (zus\u00e4tzlich g\u00fcltig ist noch XML) welches <em>Codeception<\/em> f\u00fcr die Tests verwenden soll.<\/p>\n<p>Nun k\u00f6nnen wir unseren API-Test schreiben. Hier wieder ein einfaches Beispiel:<\/p>\n<pre class=\"lang:php decode:true \">&lt;?php\r\n\r\nclass ReadingSchemaVersionCest\r\n{\r\n    \/\/ tests\r\n    public function tryToTest(ApiTester $I)\r\n    {\r\n\t    $I-&gt;wantTo('check the schema version via API');\r\n\t    $I-&gt;haveHttpHeader('Content-Type', 'application\/x-www-form-urlencoded');\r\n\t    $I-&gt;sendGET('\/schemaVersion');\r\n\t    $I-&gt;seeResponseCodeIs(200);\r\n\t    $I-&gt;seeResponseIsJson();\r\n\t    $I-&gt;seeResponseContains('\"Version\":\"2013-09-01 00:00:00 \"');\r\n    }\r\n}\r\n<\/pre>\n<p>Auch dieser Test ist wider sehr sch\u00f6n lesbar. Wir geben das Format an, welches wir verwenden, rufen die URL ab, von der wir das JSON-Ergebnis haben m\u00f6chten und pr\u00fcfen dann ob wir &#8230;<\/p>\n<ul>\n<li>&#8230; einen HTTP-Status 200 (OK) erhalten<\/li>\n<li>&#8230; unsere Antwort auch wirklich JSON ist<\/li>\n<li>&#8230; in unserer Antwort ein gewisses Ergebnis enthalten ist<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h1>Tests aufrufen und Pr\u00fcfen<\/h1>\n<p>Die Tests lassen sich nun ganz einfach wieder \u00fcber die Batch-Datei aufrufen:<\/p>\n<p style=\"padding-left: 30px;\"><em><strong>codecept run<\/strong><\/em><\/p>\n<p>Das Ergebnis wird dann gleich in der Konsole angezeigt, und sieht dann etwa so aus:<\/p>\n<p><span style=\"font-family: courier new,courier,monospace; font-size: 8pt;\">Acceptance Tests (11) &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<\/span><br \/>\n<span style=\"font-family: courier new,courier,monospace; font-size: 8pt;\">Ensure that routing to entities works (RoutingEntitiesCest::ensureRoutingtoEntities)\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Ok<\/span><br \/>\n<span style=\"font-family: courier new,courier,monospace; font-size: 8pt;\">Ensure that routing to entities by id works (RoutingEntitiesCest::ensureRoutingtoEntitiesById)\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Ok<\/span><br \/>\n<span style=\"font-family: courier new,courier,monospace; font-size: 8pt;\">Ensure that routing to entities by name works (RoutingEntitiesCest::ensureRoutingtoEntitiesByName)\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Ok<\/span><br \/>\n<span style=\"font-family: courier new,courier,monospace; font-size: 8pt;\">Ensure that routing to ingredients works (RoutingIngredientsCest::ensureRoutingToIngredients)\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Ok<\/span><br \/>\n<span style=\"font-family: courier new,courier,monospace; font-size: 8pt;\">Ensure that routing to recipes works (RoutingRecipesCest::ensureRoutingToRecipes)\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Ok<\/span><br \/>\n<span style=\"font-family: courier new,courier,monospace; font-size: 8pt;\">Ensure that routing to recipes by id works (RoutingRecipesCest::ensureRoutingToRecipesById)\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Ok<\/span><br \/>\n<span style=\"font-family: courier new,courier,monospace; font-size: 8pt;\">Ensure that routing to recipes by name works (RoutingRecipesCest::ensureRoutingToRecipesByName)\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Ok<\/span><br \/>\n<span style=\"font-family: courier new,courier,monospace; font-size: 8pt;\">Ensure that routing to recipes by name works (RoutingRecipesCest::ensureRoutingToRecipesByNameLike)\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Ok<\/span><br \/>\n<span style=\"font-family: courier new,courier,monospace; font-size: 8pt;\">Ensure that routing to recipes by name works (RoutingRecipesCest::ensureRoutingToRecipesByIngredientName)\u00a0 Ok<\/span><br \/>\n<span style=\"font-family: courier new,courier,monospace; font-size: 8pt;\">Ensure that routing to recipes by name works (RoutingRecipesCest::ensureRoutingToRecipesByIngredientNames) Ok<\/span><br \/>\n<span style=\"font-family: courier new,courier,monospace; font-size: 8pt;\">Ensure that routing to schema version works (RoutingCest::ensureRoutingToSchemaVersion)\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Ok<\/span><br \/>\n<span style=\"font-family: courier new,courier,monospace; font-size: 8pt;\">&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<\/span><\/p>\n<p>&nbsp;<\/p>\n<p>Am Ende der Zeile wird &#8222;ok&#8220; oder &#8222;fail&#8220; ausgegeben, je nach Erfolg des Tests. Anschlie\u00dfend werden die Test-Ergebnisse aufgelistet, sodass man die fehlgeschlagenen Tests analysieren kann. Detaillierte Informationen dar\u00fcber, was der Test geliefert hat, werden im Ordner _output in Form von HTML-Dateien abgelegt.<\/p>\n<p>&nbsp;<\/p>\n<p>Wenn wir nur Tests f\u00fcr eine bestimmte Suite aufrufen wollen, geben wir am Ende des Aufrufs einfach den Namen der Suite an:<\/p>\n<p style=\"padding-left: 30px;\"><em><strong>codecept run acceptance<br \/>\n<\/strong><\/em><\/p>\n<p>Dann werden nur die Tests dieser Suite aufgerufen.<\/p>\n<p>&nbsp;<\/p>\n<h1>Fazit<\/h1>\n<p>Mit <em>Codeception<\/em> lassen sich sehr leicht sch\u00f6n lesbare Tests schreiben.<\/p>\n<p>Leider fehlt ein Plugin f\u00fcr IntelliJ, sodass der Aufruf und die Auswertung des Ergebnisses \u00fcber die Kommandozeile erfolgen muss (oder dem Terminal-Fenster in IntelliJ).<\/p>\n<p>Aber die Ausgabe der Testergebnisse machen die Analyse der Tests einfach und \u00fcbersichtlich.<\/p>\n<p>So macht Testen Spa\u00df!<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>End-To-End-Tests in PHP lassen sich sehr elegant mit Codeception durchf\u00fchren. Angelehnt an Behaviour-Driven-Tests ist der Sourcecode der Tests sehr ausdrucksstark, einfach zu schreiben&hellip;<\/p>\n","protected":false},"author":1,"featured_media":435,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[18,14,9],"tags":[24,27,28,25,26,19,23],"class_list":["post-293","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-intellij","category-php","category-tools","tag-akzeptanztest","tag-bdd","tag-behavior-driven","tag-codeception","tag-end-to-end-test","tag-php","tag-test"],"_links":{"self":[{"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/posts\/293","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/comments?post=293"}],"version-history":[{"count":8,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/posts\/293\/revisions"}],"predecessor-version":[{"id":436,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/posts\/293\/revisions\/436"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/media\/435"}],"wp:attachment":[{"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/media?parent=293"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/categories?post=293"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/tags?post=293"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}