Codeception Tutorial – Automatisierte Tests für ein besseres Leben

Von Roland Golla
10 Kommentare
Codeception Tutorial HowTo

Gute Codeception Tests finden Bugs und sparen ein Drittel der Entwicklungszeit. Aber es gibt viele Vorurteile und Bedenken diese einzusetzen und in der Entwicklung zu berücksichtigen. Das Projekt wäre zu klein, der Kunde würde das nicht zahlen, die Entwickler Ressourcen würden nicht ausreichen, der Ausreden Katalog in dem Bereich Testing ist riesig. Tatsächlich fehlt es ganz einfach an Know How bei den Entwicklern und Aufklärung beim Kunden. Gute Tests bieten einen Wettbewerbsvorteil, der vor allem beim Stichpunkt Employer Branding auch eine zunehmend wichtige Rolle im Arbeitsmarkt spielt. In diesem Artikel zeige ich den praktischen Einsatz von Acceptance-, API- und PHPUnit-Tests in einem Symfony PHP Projekt mit dem Codeception Framework. Die Tests lassen sich dabei in allen PHP Projekten einsetzen.

Das Agenturgeschäft ist nicht mehr von 2010

Leider hatte sich das Agenturgeschäft viele Jahre auf den Verkauf von Stunden spezialisiert. “Wenn der Kunde FTP-Deployment haben will, dann muß er das auch zahlen”, so eine gängige Ansicht, die leider viel zu oft vertreten wird. In dem Kontext werden Entwickler auch immer dazu angehalten nicht innovativ zu sein, weil man hier ja viele Dinge einzeln verkaufen könnte, die man sonst in einem Abwasch erledigt hätte. So wurde doch tatsächlich über viele Jahre an Software-Qualität ganz bewusst gespart. Und was mit den Mittelstandskunden über Jahre gut geklappt hat, weil hier die Webseite auch immer ein fünftes Rad am Wagen war, ändert sich auf einmal ganz schnell bei modernen Startups, die mit üppigen Budgets zuverlässige und skalierbare Software wollen und ganz konkrete ziele verfolgen. Und das ist der Grund warum Software-Qualität auf einmal im Rennen ist. In Stellenausschreibungen steht man auch häufiger schon etwas von Continuous Integration. Hier muß man sich als Entwickler und Arbeitgeber in der Evolution anpassen oder stehen bleiben.

Automate all the things – Es ist in deiner Natur

Automate all the things

Automate all the things

Es liegt in der Natur des Programmierers Dinge zu automatisieren. Das ist unsere innere Passion und Leidenschaft. Leider haben das viele von uns verlernt und eine kommende Generation nicht vorgelebt bekommen. Das ist nicht gut für die PHP Szene und ihren Stellenwert Gerade für Software-Qualität muß man aber offen für sein und sie als Lebenseinstellung nehmen. Das bedeutet im wesentlichen schneller zu werden und auch Wert auf einen gewissen Stil zu legen. Das fängt mit Shortcuts in PHPStorm an und setzt sich über den generellen Einsatz von Tools fort. Zum Beispiel als Terminal oh-my-zsh einzusetzen und mit den Plugins z oder jump in das Projekt zu navigieren und dort mit “pstorm .” PhPStorm zu öffnen ist eine Art sich schneller zwischen einzelnen Projekten zu bewegen. Genauso kann man hier übrigens auch Sourcetree öffnen. Das kann man allerdings auch abschaffen, da die Integration in PhPStorm echt super ist und man auf dem Terminal auch nicht alles verlernen sollte.

Codeception Acceptance Test – Schnell eine sehr hohe Testabdeckung

Generell sind Codeception Acceptance Tests eine zuverlässige Methode schnell eine sehr hohe Testabdeckung in einem Projekt zu erzielen. Das Frontend ist bekanntlich die Summe aller Prozesse. Betrachten wir zum Beispiel die Autovervollständigung in einer Suche, so wird hier schon sichergestellt, daß es im Backend, Frontend und der Datenbank keine Fehler gibt. Und das schafft man schon mit unter 10 Zeilen Quellcode für den Test. Und Hand aufs Herz, wer testet jedes Mal die Suche manuell? Natürlich kann die bei egal welcher Änderung im Frontend oder Backend wegfliegen. Wird schon einer merken – wohl kaum – ich kenne ein Megamenu in einem Shop, das war 3 Wochen lang weg und das ist ein Marktführer.

Zwei Tests Codeception Frontend Tests als Beispiel

Das Formular
Im wesentlichen besteht die Seite von Never Code Alone aus einigen Landingpages, die gezielt für Marketing Kampagnen eingesetzt werden. Hier gibt es einen Kontakt Formular Slider. Würde das Formular nicht funktionieren es wäre für mich eine absolute Katastrophe. Deshalb teste ich es praktisch immer. Wirklich? Auch wenn ich nur die Headline ändere? Was soll da schon passieren? War ich vielleicht zwischenzeitlich in anderen Branches und sind Änderungen an der Datenbank oder Composer im Hintergrund passiert. Kurz gesagt ich kann meiner lokalen Entwicklungsumgebung nicht vertrauen.


class contactCest
{
public function _before(AcceptanceTester $I)
{
$I->amOnPage('/nca-paas-startup/');
$I->wait(5);
$I->waitForElement('#setting > i');
}

// tests
public function openContactFormAndSentWithValidData(AcceptanceTester $I)
{
$inputData = [
'name' => 'Roland',
'email' => 'rg-' . time() . '@gmail.com',
'message' => 'Testify right here'
];

$I->click('#setting > i');
$I->wait(1);
$I->selectOption('#reason', 'lunch');
$I->fillField('#namefield', $inputData['name']);
$I->fillField('#emailfield', $inputData['email']);
$I->fillField('#phone', '112');
$I->fillField('#messagefield', $inputData['message']);

$I->click('#nca-form > div > div.button > button');
$I->waitForText('Danke wir melden uns');

$inputData['message'] = 'lunch|112|Testify right here';

$I->seeInDatabase(
'message',
$inputData
);
}
}
Codeception Acceptance Database

Codeception Acceptance Database

Der eigentliche Test hat hier gerade einmal 25 Zeilen und deckt das Javascript, Routing, den AJAX Call und den richtigen Eintrag in der Datenbank sicher. Erst einmal möchte ich aber auf einige drängende Fragen eingehen. Die Zeit ist hier nicht repräsentativ und dennoch schneller als ich mit Maus und Browser einen test durchführen würde. Hier fährt immerhin ein Chrome auf meinem Mac hoch. Das dauert beim ersten Start eben ein wenig. Die hier verwendeten HTML Tags können in ein Page Objekt ausgelagert werden und so zentral oder auch pro Projekt verwaltet werden. Das zeige ich auch in dem zweiten Test. Die Tests können also in unterschiedlichen Projekten wiederverwendet werden. Wenn man data-q Attribute nutzt, dann ist man auch unabhängig von der HTML Struktur und wesentlich stabiler. Ja natürlich könnte hier auch das Errorhandling im Formular getestet werden, aber ehrlich gesagt gibt es das Feature gar nicht 😉 Und natürlich könnte man auch einen Mailcatcher anbinden und den Versand kontrollieren.

Hier sind zwei statische Waits eingebaut worden. Einmal mit 5 und einmal mit 1 Sekunde. Die 5 Sekunden liegen hauptsächlich an dem Erstaufruf der Seite im Develop Modus. Hier ist ja ein Spinning Loader im Einsatz. Der liegt über allem. Ich könnte jetzt korrekterweise auf dessen verschwinden und die Sichtbarkeit des Openers warten. Parallax Seiten haben aber “Eigenschaften” und deshalb sind diese beiden Stellen hier kurzfristig die stabilste Lösung. Wie man es richtig macht steht auch im zweiten Test.

Ich setze ein Array mit inputData auf. Diese Array kann ich genauso auch immer in der Datenbank suchen. Danach öffne ich mit einem Klick den Kontakt Slider und fülle alle Felder aus. Dann wird das Formular abgeschickt und über einen AJAX-Request verarbeitet. Bei Erfolg kommt dann ein Text zurück. Das Beispiel zeigt, wie einfach es ist auf einen Text zu warten. Da die Message alle Informationen des Formulars enthält muß ich diese für die Überprüfung überschreiben. Hier könnte die Message noch in eine Variable und auch geschaut werden, ob es nur einen Datensatz mit dem konkreten Angaben gibt. Das ist für mich an der Stelle aber nicht relevant, da ich hier keine Spam Protection kontrolliere, sondern tatsächlich “Open contact form and sent with valid data”. Der Name vom Test wird also auf der CLI und für den Report richtig aufgelöst.

Der Slider Test


class servicesCest
{
    public function _before(AcceptanceTester $I)
    {
        $I->amOnPage('/');
        $I->waitForPageLoad();
    }

    public function navigationMatchesAnchors(AcceptanceTester $I, startpage $startpage)
    {
        $anchors = $I->grabMultiple($startpage::$servicesNavigation, 'href');
        $contentItems = $I->grabMultiple($startpage::$servicesItems, 'id');

        foreach ($anchors as $key => $anchor) {
            $anchorValue = explode('#', $anchor)[1];
            $I->assertEquals($anchorValue, $contentItems[$key]);
        }
    }

    public function onlyFirstContentElementIsVisibleOnStart(AcceptanceTester $I, startpage $startpage)
    {
        $contentItems = $I->grabMultiple($startpage::$servicesItems, 'id');

        $first = true;
        foreach ($contentItems as $contentItem) {
            // First item is visible
            if($first) {
                $I->seeElement('#' . $contentItem);
                $first = false;
                continue;
            }

            $I->cantSeeElement('#' . $contentItem);
        }
    }

    public function onClickElementIsVisibleAndAllOthersNot(AcceptanceTester $I, startpage $startpage)
    {
        $I->scrollTo($startpage::$serviceSection);

        $anchors = $I->grabMultiple($startpage::$servicesNavigation, 'href');
        $contentItems = $I->grabMultiple($startpage::$servicesItems, 'id');

        array_shift($anchors);
        $firstItem = array_shift($contentItems);

        $I->seeElement('#' . $firstItem);

        foreach ($anchors as $key => $anchor)
        {
            $I->click('//*[@data-q="services-navigation"]/li[' . ($key + 2) . ']/div/a');
            $I->waitForElementVisible('#' . $contentItems[$key]);

            $actualContentItems = $I->grabMultiple($startpage::$servicesItems, 'id');
            foreach ($actualContentItems as $actualContentItem) {
                if($actualContentItem === $contentItems[$key]) {
                    continue;
                }

                $I->cantSeeElement('#' . $actualContentItem);
            }
        }

        $I->click('//*[@data-q="services-navigation"]/li[1]/div/a');
        $I->waitForElementVisible('#' . $firstItem);
    }
}

Der Multislider ist das wichtigste Element für das Marketing

Codeception run ServiceCest

Codeception run ServiceCest

Der Multislider von Never Code Alone ist ein komplexes Content Element und auch das wichtigste Tool beim Marketing. Hier wird das Leistuingsangebot der Never Code Alone Events abgebildet und potentielle Kunden werden im Rahmen der Telefon-Aquise an diese Stelle geführt. Aber auch auf Veranstaltungen und beim Kunden wird mit genau diesem Element gearbeitet. Hier kann man sehr einfach konkrete Beispiele zu den einzelnen Leistungspunkten herausstellen. Video-Marketing, Socialmedia-Marketing, die Vor- und Nachberichte werden hier als Pakete abgebildet und zahlreiche Beispiele gezeigt. Aber da es hier kein CMS gibt und alle Inhalte per Copy&Pase manuell gepflegt werden ist er auch sehr fehleranfällig.

Die Features des Multi Sliders

Codeception Step Output Command Line

Codeception Step Output Command Line

Der Multi Slider ist ein Slider mit mehreren Kategorien. Die Kategorien bilden sich in einer seitlichen Navigation ab. Als Standard ist bei einem ersten Seitenaufruf das erste Element sichtbar und alle anderen sind nicht sichtbar. Wählt man dann als User eine Slider Kategorie aus der Navigation aus wird diese sichtbar und alle anderen unsichtbar. Die einzelnen Slides lassen sich dann auf Klick navigieren.

Die Slider Tests im Detail

navigationMatchesAnchors
Mit Codeception grapMultiple kann man mehrere Elemente in Array speichern. In diesem Test sind die Selektoren bereits ausgelagert. Die Assertion schaut, ob die Anker zu den Content Items passen. Der Test ist mit seinen 10 Zeilen sehr überschaubar, zeigt aber Fehler einfacher an, als der eigentliche Klick Test. So wird ein einfacher Syntax Bug sehr schnell erkannt.

onlyFirstContentElementIsVisibleOnStart
Hier wird geprüft, ab der erste Slider sichtbar und alle anderen unsichtbar sind, wenn man die Seit einfach nur aufruft. Das ist der erste Zustand des Sliders. Codeception seeElement und cantSeeElement berücksichtigen den CSS Status und auch Layer, die darüber liegen könnten. Es geht hier allerdings nicht um den sichtbaren Bereich im Browser.

onClickElementIsVisibleAndAllOthersNot
Hier werden alle Elemente mit der Maus geklickt. Das bedeutet man muß erstmal an die richtige Stelle scrollen. Dann nimmt man sich wieder die Anker und ContentItems und interiert darüber. Vorher wird ebenfalls geguckt, ob das erste Element aktiv ist. Das passiert hier schon mit data-q Attributen. Dabei wird geschaut, ob alle anderen Elemente unsichtbar sind. Am Ende wird dann auch nochmal geschaut, ob das erste Element auch wieder funktioniert.

Check Slide Links – Performance ist immer wichtig

Finish Codeception Tests Genereate Report

Finish Codeception Tests Genereate Report

Mit Codeception Acceptance Tests versetzt man sich in den User. Hier eine Idee für einen möglichen weiteren Test. Alle Inhalte aus dem Slider verlinken zu weiterführenden Informationen, die für den User entscheidend sind, damit er zu einem möglichen Kunden wird. Das Ziel des Tests wäre es also alle diese Links zu validieren. Wir wollen hier nicht testen, ob die Links alle geklickt werden können. Natürlich gibt es immer unterschiedliche Wege als Programmierer ein Ziel zu erreichen. Aber Tests müssen auch immer schnell sein. Sonst werden sie nicht mehr ausgeführt. Wir holen also alle Links aus den Slider Items und checken hier kurz mit Curl auf 200. Natürlich kann man das auch auf alle Links anwenden. Bravo diesen Test kann man auf allen Seiten und in allen Projekten sinnvoll einsetzen.

API Tests

Codeception ist ein Test Framework. Das heißt es gibt einem Methoden zur Verfügung, die komplexe Prozesse wrappen. Die leichte Syntax zeichnet das Framework aus. Da APIs immer mehr in Form von Microservices Anwendung finden sind Tests hier eine ganz wichtige Sache.

Der Test


class messagesCest
{
    public function addMessageWithValidNameAndEmail(ApiTester $I)
    {
        $microtime = microtime();

        $name = 'test';
        $email = 'test' . time() . '@ify.com';
        $message = 'CC message:' . $microtime;

        $messagePost = [
            'name'    => $name,
            'email'   => $email,
            'message' => $message
        ];


        $I->haveHttpHeader('Content-Type', 'application/json');
        $I->sendPOST(
            '/api/messages',
            json_encode($messagePost)
        );

        $I->seeResponseIsJson();

        $response = json_decode($I->grabResponse(), true);

        $I->assertEquals('messagesAction', $response);
        $I->seeResponseCodeIs(200);
        $I->seeNumRecords(1, 'message', $messagePost);
    }

    public function singleEmptyValueMessageStatus(ApiTester $I)
    {
        $time = microtime();

        $name = 'test:' . $time;
        $email = 'test@ify.com';
        $message = 'This is a test:' . $time;

        $messagePost = [
            'name'    => $name,
            'email'   => $email,
            'message' => $message
        ];

        foreach ($messagePost as $key => $value) {
            $postArray = $messagePost;
            $postArray[$key] = '';

            $I->haveHttpHeader('Content-Type', 'application/json');
            $I->sendPOST(
                '/api/messages',
                json_encode($postArray)
            );

            $I->seeResponseIsJson();

            $response = json_decode($I->grabResponse(), true);

            $I->assertContains('empty', $response);
            $I->seeResponseCodeIs(400);
            $I->dontSeeInDatabase('message', $postArray);
        }
    }

    public function singleNotSetValueMessageStatus(ApiTester $I)
    {
        $time = microtime();

        $name = 'test:' . $time;
        $email = 'test@ify.com';
        $message = 'This is a test:' . $time;

        $messagePost = [
            'name'    => $name,
            'email'   => $email,
            'message' => $message
        ];

        foreach ($messagePost as $key => $value) {
            $postArray = $messagePost;
            unset($postArray[$key]);

            $I->haveHttpHeader('Content-Type', 'application/json');
            $I->sendPOST(
                '/api/messages',
                json_encode($postArray)
            );

            $I->seeResponseIsJson();

            $response = json_decode($I->grabResponse(), true);

            $I->assertContains('not set', $response);
            $I->seeResponseCodeIs(401);
            $I->dontSeeInDatabase('message', $postArray);
        }
    }
}

Codeception Formular Test

Codeception Formular Test

Die Geschwindigkeit ist hier natürlich deutlich höher und eignet sich für die produktive Arbeit mit Tests. Hier kann man auch mit TDD – Test Driven Development arbeiten. Generell kann man festhalten, daß Tests die nicht ständig ausgeführt werden verfallen und Probleme machen. Hier ist es durchaus möglich, gerade bei Frontend Tests, daß sie gar keine richtigen Fehler finden, sondern nur angepasst werden müssten. Deshalb verlieren diese Tests an Vertrauen. Denn es geht ja alles und trotzdem schlägt der Test fehl. Und da komplexe Tests auch nicht so gerne von Kollegen gelesen und analysiert werden hat man hier leider sehr schnell ein Problem. Deshalb lieber kleine und gute Tests als riesige User Stories. Ich gehe hier einmal auf den ersten Test ein, da beide anderen Tests invalide Daten, den Error Code und ob es nicht in der Datenbank ankommt checken. Sie sind dem ersten Test als sehr ähnlich.

Microtime statt Time

Codeception API Test

Codeception API Test

Die Codeception Tests sind so schnell, daß sie bei der Nutzung der time()-Methode für die Überprüfung von Duplicate Content in der DB fehlschlagen würden. Es wäre dann in derselben Sekunde. Das muß man beachten, wenn man beispielsweise E-Mail Adressen mit einem Zeitstempel versieht.

Hier sind die Input Daten im Array messagePost und werden so auch wieder in der Datenbank gesucht. Hier wird auch explizit nach nur einem Datensatz gesucht. Man kann ganz einfach Post, Get, Put etc. Requests gegen eine API Route schicken. Auf den Response kann man einige Assertions direkt anwenden. Hier brauche ich ein seeResponseIsJson und seeResponseCodeIs. Für die Validierung des Response kann ich diesen in ein Array übernehmen und mit den PHPUnit Assertions genauer verifizieren. Die werden als Modul in Codeception extra aktiviert und sind sehr hilfreich.

PHPUnit Test


class spamProtectionCest
{
    private $fixture;
    private $I;

    public function _before(UnitTester $I)
    {
        $this->I = $I;
        $this->fixture = new SpamProtection();
    }

    // tests
    public function validateUserInputsReturnTrueOnValidData(UnitTester $I)
    {
        $this->fixture = Stub::make(
            $this->fixture,
            [
                'validateName' => true,
                'validateEmail' => true,
                'validateMessage' => true,
                'validateIp' => true
            ]
        );

        $data = [
            'name' => '',
            'email' => '',
            'message' => '',
            'ip' => ''
        ];

        $I->assertTrue($this->fixture->validateUserInputs($data));
    }

    public function validateNameEmptyIsFalse(UnitTester $I)
    {
        $methodReturn = $this->getMethodReturn('validateName', '');
        $I->assertFalse($methodReturn);
    }

    public function validateNameIsSpam(UnitTester $I)
    {
        $methodReturn = $this->getMethodReturn('validateName', 'viagra');
        $I->assertFalse($methodReturn);
    }

    /**
     * @dataProvider invalidIpsProvider
     */
    public function validateIpWithInvalidIpsReturnFalse(UnitTester $I, Example $data)
    {
        $ip = $data[0];
        $methodReturn = $this->getMethodReturn('validateIp', $ip);
        $I->assertFalse($methodReturn, 'IP: ' . $ip);
    }
    /**
     * @return array
     */
    protected function invalidIpsProvider() // alternatively, if you want the function to be public, be sure to prefix it with `_`
    {
        return [
            [123],
            ['192.168.0.1']
        ];
    }

    /**
     * @dataProvider validIpsProvider
     */
    public function validateIpWithValidIpsReturnFalse(UnitTester $I, Example $data)
    {
        $this->fixture = Stub::make(
            $this->fixture,
            [
                'isIpFromDe' => true
            ]
        );

        $ip = $data[0];
        $methodReturn = $this->getMethodReturn('validateIp', $ip);
        $I->assertTrue($methodReturn, 'IP: ' . $ip);
    }

    /**
     * @return array
     */
    protected function validIpsProvider() // alternatively, if you want the function to be public, be sure to prefix it with `_`
    {
        return [
            ['176.95.142.6'] // DE
        ];
    }

    public function validateEmailEmptyStringReturnFalse(UnitTester $I)
    {
        $methodReturn = $this->getMethodReturn('validateEmail', '');
        $I->assertFalse($methodReturn);
    }

    public function validateEmailNotValidEmailReturnFalse(UnitTester $I)
    {
        $methodReturn = $this->getMethodReturn('validateEmail', 'testify');
        $I->assertFalse($methodReturn);
    }

    public function validateEmailValidEmailReturnTrue(UnitTester $I)
    {
        $methodReturn = $this->getMethodReturn('validateEmail', 'test@testify.com');
        $I->assertTrue($methodReturn);
    }

    public function validateMessageEmptyReturnFalse(UnitTester $I)
    {
        $methodReturn = $this->getMethodReturn('validateMessage', '');
        $I->assertFalse($methodReturn);
    }

    public function validateMessageSpamWordsReturnFalse(UnitTester $I)
    {
        $spamProtection = new SpamProtection();
        $spamWords = $spamProtection->spamWords;

        foreach ($spamWords as $spamWord) {
            $methodReturn = $this->getMethodReturn('validateMessage', $spamWord);
            $I->assertFalse($methodReturn, $spamWord);
        }
    }

    public function validateMessageWithLowerUpperMixSpamWords(UnitTester $I)
    {
        $spamWords = [
            'VIAGRA',
            'ViAgRa',
            'VIAgra'
        ];

        foreach ($spamWords as $spamWord) {
            $methodReturn = $this->getMethodReturn('validateMessage', $spamWord);
            $I->assertFalse($methodReturn, $spamWord);
        }
    }

    /**
     * @throws \ReflectionException
     */
    private function getMethodReturn($method, $param)
    {
        $class = new \ReflectionClass($this->fixture);
        $method = $class->getMethod($method);
        $method->setAccessible(true);
        $methodReturn = $method->invoke($this->fixture, $param);
        return $methodReturn;
    }



}

Schneller als sein Schatten und unglaublich mächtig

Codeception Unit Test

Codeception Unit Test

Codeception PHPUnit Tests sind unglaublich schnell und brauchen überhaupt keine vollständige oder lauffähige Umgebung. Sie können also in einem viel früheren Stadium eines Builds ausgeführt werden. Dadurch haben sie einen weiteren starken Vorteil, weil sie viel schneller Feedback liefern können. Durch die Kapselung der Tests von allen anderen Units finden sie auch Bugs, die genau an der betreffenden Stelle sind. Denn am Ende testen sie schließlich nur das genaue Unit und des Funktionalität. Hier wird ja der Test validateUserInputsReturnTrueOnValidData ausgeführt. Und der stellt nur sicher, daß die Methode validateUserInputs true zurück gibt, wenn alle Methoden zur Prüfung grünes Licht geben. Es ist hier nicht relevant, ob die IP richtig ist. Denn dafür ist eine andere Methode zuständig. Das ist am Anfang ein wenig gewöhnungsbedürftig, hat aber am Ende viele zeitliche und inhaltliche Vorteile. Vor allem wird die Fehlersuche viel effizienter.

Codeception macht das Mocken so einfach

Codeception HTML Report

Codeception HTML Report

Das wichtigste bei der keder Applikation sind Features. Die werden bezahlt und die bringen uns weiter. Hier habe ich eine Formular Validierung und Spam Protection gebaut. Sicherlich bringt das Symfony Framework bereits viele sehr gute Validatoren mit, aber da ich die sehr individuell gestalten möchte und auch Framework unabhängig einsetzen möchte habe ich mich hier für eine eigene Lösung entschieden. Das ganze steht als Service in der Applikation zur Verfügung und vielen Dank noch einmal an dieser Stelle für die Symfony 4 Entwickler und das neue Autowireing. Codeception Unit Tests testen immer nur exakt ein Unit und nicht die darüber hinausgehende Abhängigkeit. Das bedeutet das Abhängigkeiten innerhalb der Methode gemockt werden. Es sind eben keine Functional- oder Integrationtests. Betrachten wir dazu einmal den ersten Test im Detail


public function validateUserInputsReturnTrueOnValidData(UnitTester $I)
    {
        $this->fixture = Stub::make(
            $this->fixture,
            [
                'validateName' => true,
                'validateEmail' => true,
                'validateMessage' => true,
                'validateIp' => true
            ]
        );

        $data = [
            'name' => '',
            'email' => '',
            'message' => '',
            'ip' => ''
        ];

        $I->assertTrue($this->fixture->validateUserInputs($data));
    }

Obwohl ich ausschließlich leere Werte als data-Array in die Methode validateUserInputs übergebe kommt ein true zurück. Mit der extrem einfachen Syntax von Codeception kann ich einfach ein Stub machen indem ich festlege was eine Methode von außerhalb zurück gibt. Das kann ich auch mit anderen Klassen machen. Da alle Validator-Methoden jetzt true zurückgeben macht das auch die validateUserInputs. Das bedeutet also im wesentlichen erstmal, daß ich mir keine komplizierten Setups ausdenken muß, um z.B. zu schauen, ob eine IP jetzt aus Deutschland kam. Ich prüfe hier ja nur, wenn alle Daten valide wären, daß true zurück kommt. Denn das ist alles was die Methode tut. Genauso könnte ich auch einen Test schreiben, der immer eine Methode auf false setzt und dann auf false validiert.


Protected Methoden testen und Data Provider

/**
     * @throws \ReflectionException
     */
    private function getMethodReturn($method, $param)
    {
        $class = new \ReflectionClass($this->fixture);
        $method = $class->getMethod($method);
        $method->setAccessible(true);
        $methodReturn = $method->invoke($this->fixture, $param);
        return $methodReturn;
    }

/**
     * @dataProvider invalidIpsProvider
     */
    public function validateIpWithInvalidIpsReturnFalse(UnitTester $I, Example $data)
    {
        $ip = $data[0];
        $methodReturn = $this->getMethodReturn('validateIp', $ip);
        $I->assertFalse($methodReturn, 'IP: ' . $ip);
    }

    /**
     * @return array
     */
    protected function invalidIpsProvider()    {
        return [
            [123],
            ['192.168.0.1']
        ];
    }

Sollen Methoden die nicht public sind getestet werden. Ich finde ja, weil sie jeweils separate Features für die Applikation liefern und ich so viel genauer die richtige Stelle testen kann. Deshalb habe ich mir eine Methode gebaut, die Methoden mit Hilfe einer ReflectionClass public setzen kann und den Return zurück gibt. Betrachten wir dazu die validateIpWithInvalidIpsReturnFalse Methode. Hier werden über einen Data Provider zwei IPs übergeben und nacheinander getestet. Gerade bei mehreren zu überprüfenden Werten ist das sehr praktisch.

Fazit zum Thema Codeception Testing

Codeception Tests sind ein Werkzeug, daß jeder Entwickler der es kennt zu schätzen weiß und ihm viel Zeit bringt. Wichtige Zeit, die für Refactoring und Verbesserungen verwendet werden kann. Dadurch ist es möglich sehr gute Software zu produzieren. Ohne Tests ist das absolut nicht möglich, weil man im Legacy Code steckt und sich auch immer wieder um manuelle Deployments und Basis Tests kümmern muß. Weil so aber keine wirklichen Bugs gefunden werden steigt die Change-Fail-Rate. Also immer wenn man was ändert fliegt was anderes weg. Als Entwickler gerät man so stark unter Druck von allen Seiten, da man in die alleinige und volle Verantwortung genommen wird ein stabiles Kartenhaus auszuliefern. Das macht auf Dauer weich und krank. Krankheitsbilder wie Anpassungsschwierigkeiten oder gar Burnouts sind langwierig und wirklich gefährlich. Das ist nicht zu unterschätzen.

Wir helfen gerne

Alle Projekte und unser gesamtes Schulungs und Präsentationsmaterial ist öffentlich als Open Source auf den Github Repositories von Never Code Alone und der Entwicklungshilfe NRW verfügbar. Hier gibt es auch den gesamten Quellcode von den Beispielen hier oben auf https://github.com/nevercodealone/nevercodealone. Unsere Präsentationen halten wir gerne auf Meetups, Usergroups und Firmen und auf Konferenzen. Sprecht uns dafür einfach an.

Social Media

Dazu sind wir auf drei Social Media Kanälen aktiv. Auf Facebook gibt es regelmäßig die #NCADiscussion. Hier werden Arbeitsbedingungen von Entwicklern diskutiert und versucht sich über Probleme auszutauschen. Auf Instagram gibt es das Developer Tagebuch von mir selbst und auf Twitter hilfreiche Links für die Entwicklung und Retweets von wichtigen Tweets. Folgen lohnt sich

Roland Golla ist Speaker und Trainer bei der Entwicklungshilfe NRW und bringt mit Never Code Alone gute Arbeitgeber mit guten Web Developern zusammen.

10 Kommentare

Tutorials und Top Posts

10 Kommentare

Groovy Shell Scripting - Teil 1 - Groovy auf der Shell installieren und verwenden 12. August 2018 - 0:43

[…] ihr die zsh, vielleicht sogar mit oh-my-zsh verwendet, wie es Roland hier schon empfohlen hat könnt ihr diese Zeilen auch einfach ans Ende eurer .zshrc kopieren und alles funktioniert. […]

Reply
Hall of Fame #NCAEvent – PHP-Training in der Community 6. November 2018 - 13:28

[…] VueJs um. Nach der Mittagspause kommt das Rafal und zeigt TDD – Test Driven Development – mit PHPUnit und einem Preis-Importer in einer Symfony 4 E-Commerce-Anwendung. Hier lernen wir, wie wichtig […]

Reply
10. Employer-Branding-Maßnahmen für PHP-Jobs 3. Dezember 2018 - 14:20

[…] in einem zeitlich vertretbaren Rahmen abbilden, geschweige denn mehrfach und kontinuierlich – Continuations Integration – leisten. Dazu ist es natürlich auch wichtig mit PHPUnit- und Functional-Tests zu arbeiten. Die […]

Reply
Lars 14. Dezember 2018 - 21:07

„Multislider ist das wichtigste Element für das Marketing“ würde ich so nicht unterschreiben. Hatten vor einigen Jahren einen Relaunche wo wir uns gegen einen neuen Slider entschieden haben, da wir in Google Analytics gesehen haben, dass die Clickrate pro Slide dramatisch abgenommen hat. Ist aber wahrscheinlich von Projekt zu Projekt unterschiedlich.

Hier noch einige Argumente gegen Slider:
– Sie verursachen Bannerblindheit
– Sie teilen die Aufmerksamkeit Ihres Benutzers
– Das menschliche Auge reagiert nicht gut auf Bewegung
– Sie nehmen Ihren Besuchern die Kontrolle
– Sie nehmen Platz ein … und werden kaum angeklickt
– Sie reduzieren die Sichtbarkeit
Quelle: https://instapage.com/blog/6-reasons-why-image-sliders-are-bad-for-conversions

Reply
Codeception Tests finden selber Bugs – PHP Training lohnt sich direkt 20. Januar 2019 - 15:48

[…] PHP-Entwicklerteam von Logsol ist richtig gut drauf und hatte sich bereits im Vorfeld mit dem Codeception PHP-Testing-Framework auseinander gesetzt. Genau auf dieses Framework hat sich PHP-Trainer Roland Golla spezialisiert und […]

Reply
Continuous Integration PHP-Training – Build Pipeline in Leipzig mit den Code Fellas 14. Februar 2019 - 23:54

[…] NRW hat sich seit Jahren auf PHP-Trainings im Bereich Continuous Integration mit dem Codeception PHP-Testing Framework […]

Reply
Der Pott codet Java - Accenture Interactive #NCAEvents 2019 in Dortmund 1. März 2019 - 14:33

[…] im Software Development einsetzt – da sind Pair Programming, Wissensaustausch und Feedback sowie Testing und Code Reviews nur einige der Maßnahmen, die Roland auf Talks und Meetups immer wieder vorstellt […]

Reply
TYPO3 Camp Venlo 2019 Review Blog Post Codeception Workshop 28. März 2019 - 20:18

[…] in Venlo treffen sich jährlich sehr gute Community-Entwickler und dieses Jahr gab es auch eine Codeception-PHP-Schulung als Workshop. Das dreitägige Event ist in der TYPO3-Szene fest etabliert und zeichnet sich […]

Reply
Codeception Workshop bei der RHEINPFALZ - Automatisches Testing 16. Juni 2019 - 13:10

[…] in Person von Roland Golla, in der RHEINPFALZ. Um was sollte es also gehen – kurz und knapp: Testing mit Codeception und Vorbereitung für GitLab Pipelines. Wir, das Entwickler-Team der RHEINPFALZ, nahmen mit […]

Reply
PHPStan und Code Standard – PHP-Training für Teams mit PHPUnit 6. Oktober 2019 - 23:20

[…] der erste Tag für den Einstieg eine praktische Best-Practice-Demo in der Roland Golla selber ein Test-Setup und viel Know-How in einem ganz neuen Repository aufsetzt. Danach geht es mit der Funktastatur an die zweite User Story. Hier werden alle Fragen geklärt und […]

Reply

Gib uns Feedback

Diese Seite benutzt Cookies. Ein Akzeptieren hilft uns die Seite zu verbessern. Ok Mehr dazu