PHP Elegant Testing in Laravel

Rate this post

Die Programmiersprache PHP ist seit Jahrzehnten im Bereich der Webanwendungen für viele Entwickler die erste Wahl. Seit der Einführung objektorientierter Sprachfeatures mit der Version 5 wurde PHP erwachsen. Große Projekte lassen sich nun in eine saubere und vor allem wartbare Architektur bringen. Ein markanter Unterschied zwischen kommerzieller Softwareentwicklung und einem Hobby-Programmierer, der die Vereinshomepage zusammengebaut hat und betreut, ist der automatisierte Nachweis, dass die Anwendung festgelegte Vorgaben einhält. Hiermit betreten wir also das Terrain der automatisierten Softwaretests.

Ein wichtiger Grundsatz von automatisierten Softwaretests ist, dass diese ohne zusätzliche Interaktion nachweisen, dass die Anwendung ein zuvor festgesetztes Verhalten an den Tag legt. Softwaretests können nicht sicherstellen, dass eine Anwendung fehlerfrei ist, dennoch erhöhen sie die Qualität und reduzieren die Menge der möglichen noch enthaltenen Fehler. Der wichtigste Aspekt in automatisierten Softwaretests ist, dass ein bereits in Tests formuliertes Verhalten jederzeit in kurzer Zeit überprüft werden kann. Das stellt sicher, dass, wenn Entwickler eine bestehende Funktion erweitern oder in ihrer Ausführungsgeschwindigkeit optimieren, die vorhandene Funktionalität nicht beeinflusst wird. Kurz gesagt, haben wir ein leistungsfähiges Mittel, um sicherzustellen, dass wir bei unserer Arbeit nichts kaputtprogrammiert haben, ohne mühselig alle Möglichkeiten von Hand aus jedes Mal aufs Neue durchzuklicken.

Fairerweise muss man auch erwähnen, dass die automatisierten Tests entwickelt werden müssen, was wiederum im ersten Moment Zeit kostet. Dieser ‚vermeintliche‘ Mehraufwand kompensiert sich aber zügig, sobald die Testfälle mehrfach ausgeführt werden, um sicherzustellen, dass der Status Quo sich nicht verändert hat. Natürlich gehört es auch dazu, dass die erstellten Testfälle ebenfalls gepflegt werden müssen.

Wird etwa ein Fehler erkannt, schreibt man zuerst für diesen Fehler einen Testfall, der diesen Fehler nachstellt. Die Reparatur ist dann erfolgreich abgeschlossen, wenn der beziehungsweise die Testfälle erfolgreich sind. Aber auch Änderungen im Verhalten vorhandener Funktionalität erfordern immer ein entsprechendes Anpassen der zugehörigen Tests. Dieses Konzept, zur Implementierung der Funktion parallel Tests zu schreiben, ist in vielen Programmiersprachen umsetzbar und wird testgetriebene Entwicklung genannt. Aus eigener Erfahrung empfehle ich, auch bei vergleichsweise kleinen Projekten bereits testgetrieben vorzugehen. Kleine Projekte haben oft nicht die Komplexität großer Anwendungen, für die auch im Bereich des Testens einige Tricks benötigt werden. In kleinen Projekten hat man hingegen die Möglichkeit, im überschaubaren Rahmen seine Fertigkeiten auszubauen.

Testgetriebene Softwareentwicklung ist auch in PHP keine neue Sache. Das Unit-Test Framework PHPUnit von Sebastian Bergmann gibt es bereits seit 2001. Das um 2021 erschiene Test-Framework PEST setzt auf PHPUnit auf und erweitert dies um eine Vielzahl an neuen Möglichkeiten. PEST steht für PHP elegant Testing und definiert sich selbst als ein Werkzeug der neuen Generation. Da viele, vor allem kleinere Agenturen, die in PHP ihre Software entwickeln, in aller Regel auf manuelles Testen beschränken, möchte ich mit diesem kleinen Artikel eine Lanze brechen und aufzeigen, wie leicht es ist, PEST zu nutzen. Natürlich gibt es zu dem Thema testgetriebene Softwareentwicklung ein Füllhorn an Literatur, die auf die Art und Weise, wie man Tests in einem Projekt möglichst optimal organisiert. Dieses Wissen ist ideal für Entwickler, die bereits erste Gehversuche mit Test-Frameworks gemacht haben. Denn in diesen Büchern kann man lernen, wie man mit möglichst wenig Aufwand unabhängige, wartungsarme und performante Tests entwickelt. Um aber an diesen Punkt zu gelangen, muss man zuerst einmal die Einstiegshürde, die Installation der gesamten Umgebung, bewerkstelligen.

Eine typische Umgebung für eigene entwickelte Web-Projekte ist das Laravel-Framework. Beim Anlegen eines neuen Laravel-Webprojektes besteht die Möglichkeit, sich zwischen PHPUnit und PEST zu entscheiden. Laravel kümmert sich um alle notwendigen Details. Als notwendige Voraussetzung wird eine funktionierende PHP Umgebung benötigt. Dies kann zum einen ein Docker Container sein, eine native Installation oder die Serverumgebung XAMPP von Apache Friends. Für unser kurzes Beispiel verwende ich die PHP CLI in einem Debian Linux.

sudo apt-get install php-cli php-mbstring php-xml php-pcov

Nach Ausführen des Kommandos in der Konsole kann über den Befehl php -v der Erfolg der Installation getestet werden. Im nächsten Schritt benötigen wir einen Paketmanager, mit dem wir andere PHP Bibliotheken für unsere Anwendung bereitstellen können. Composer ist ein solcher Paketmanager. Dieser ist mit wenigen Anweisungen ebenfalls schnell auf dem System bereitgestellt.

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === 'ed0feb545ba87161262f2d45a633e34f591ebb3381f2e0063c345ebea4d228dd0043083717770234ec00c5a9f9593792') { echo 'Installer verified'.PHP_EOL; } else { echo 'Installer corrupt'.PHP_EOL; unlink('composer-setup.php'); exit(1); }"
php composer-setup.php
php -r "unlink('composer-setup.php');"

Damit wird die aktuelle Version der Datei composer.phar in das aktuelle Verzeichnis heruntergeladen, in dem der Befehl ausgeführt wird. Zudem wird auch automatisch der korrekte Hash überprüft. Damit Composer auch global über die Kommandozeile verfügbar ist, kann entweder der Pfad in die Pfadvariable aufgenommen werden oder man setzt eine Link der composer.phar in ein Verzeichnis, dessen Pfad bereits in der Bash integriert ist. Ich bevorzuge letztere Variante und erreiche dies über:

ln -d composer.phar $HOME/.local/bin/composer

Wenn alles korrekt ausgeführt wurde sollte nun mit composer list die Version inklusive der verfügbaren Kommandos ausgegeben werden. Ist dies der Fall, können wir global in das Composer Repository den Lavarel Installer installieren.

php composer global require laravel/installer

Damit wir nun auch über die Bash Lavarel installieren können, muss die Path-Variable COMPOSER_HOME gesetzt werden. Um herauszufinden, wo Composer das Repository angelegt hat, genügt der Befehl composer config -g home. Den hierüber ermittelten Pfad, der in meinem Fall /home/ed/.config/composer lautet, bindet man dann in die Variable COMPOSER_HOME. Nun können wir in einem leeren Verzeichnis

php $COMPOSER_HOME/vendor/bin/laravel new MyApp

ausführen, um ein neues Laravel-Projekt anzulegen. Die zugehörige Ausgabe auf der Konsole schaut wie folgt aus:

ed@P14s:~/Downloads/test$ php $COMPOSER_HOME/vendor/bin/laravel new MyApp

   _                               _
  | |                             | |
  | |     __ _ _ __ __ ___   _____| |
  | |    / _` |  __/ _` \ \ / / _ \ |
  | |___| (_| | | | (_| |\ V /  __/ |
  |______\__,_|_|  \__,_| \_/ \___|_|


  Which starter kit would you like to install? ────────────────┐
  None                                                         
 └──────────────────────────────────────────────────────────────┘

  Which testing framework do you prefer? ──────────────────────┐
  Pest                                                         
 └──────────────────────────────────────────────────────────────┘

Creating a "laravel/laravel" project at "./MyApp"
Installing laravel/laravel (v12.4.0)
  - Installing laravel/laravel (v12.4.0): Extracting archive
Created project in /home/ed/Downloads/test/MyApp
Loading composer repositories with package information

Die so erzeugte Verzeichnisstruktur enthält den Ordner tests, in dem die Testfälle abgelegt sind, sowie die Datei phpunit.xml, welche die Konfiguration der Tests vorhält. Laravel definiert zwei Test Suiten: Unit und Feature, die bereits jeweils einen Demo Test enthalten. Um die beiden Demotestfälle auszuführen, nutzen wir das von Laravel mitgelieferte Kommandozeilenwerkzeug artisan [1]. Um die Tests auszuführen, genügt im Root-Verzeichnis einzig der Befehl php artisan test.

Damit wir die Qualität der Testfälle beurteilen können, müssen wir die zugehörige Testabdeckung ermitteln. Die Coverage erhalten wir ebenfalls durch artisan mit der Anweisung test, die durch den Parameter –coverage ergänzt wird.

php artisan test --coverage

Für die von Lavarel mitgelieferten Demo Testfälle ist die Ausgabe wie folgt:

Leider sind die Möglichkeiten von artisan zum Ausführen der Testfälle sehr eingeschränkt. Um den vollen Funktionsumfang von PEST nutzen zu können, sollte von Beginn an gleich der PEST Exekutor verwendet werden.

php ./vendor/bin/pest -h

Den PEST Exekutor findet man im Verzeichnis vendor/bin/pest und mit dem Parameter -h wird die Hilfe ausgegeben. Neben diesem Detail beschäftigt uns der Ordner tests, den wir bereits erwähnt haben. Im initialen Schritt sind über die Datei phpunit.xml zwei Testsuiten vorkonfiguriert. Die Testdateien selbst sollten mit dem Suffix Test enden, wie im Beispiel ExampleTest.php.

Im Vergleich zu anderen Test Suiten, versucht PEST möglichst viele Konzepte der automatisierten Testausführung zu unterstützen. Um dabei den Durchblick nicht zu verlieren, sollte jede Teststufe in einer eigenen Test Suite abgelegt werden. Neben den klassischen UnitTests werden Browsertests, Stresstests, Architekturtests und sogar das neu aufgekommene Mutation Testing ermöglicht. Natürlich kann dieser Artikel nicht alle Aspekte von PEST behandeln, zudem sind mittlerweile auch viele hochwertige Tutorials für das Schreiben klassischer Komponententests in PEST verfügbar. Deswegen beschränke ich mich auf einen Überblick und ein paar weniger verbreitete Konzepte.

Architektur-Test

Der Sinn von Architekturtests ist es auf einfache Weise zu überprüfen, ob die Vorgaben durch die Entwickler auch eingehalten werden. Dazu zählt, dass unter anderem Klassen, die Datenmodelle repräsentieren, in einem festgelegten Verzeichnis liegen und nur über spezialisierte Klassen aufgerufen werden dürfen.

test('models')
->expect('App\Models')
->toOnlyBeUsedOn('App\Repositories')
->toOnlyUse('Illuminate\Database');

Mutation-Test

Diese Form des Testens ist etwas Neues. Zweck der Übung ist es durch Veränderungen, z. B. in Bedingungen der originalen Implementierungen, sogenannte Mutanten zu erzeugen. Wenn die zu den Mutanten zugeordneten Tests weiterhin korrekt durchlaufen werden, anstatt fehl zuschlagen, kann das ein starker Hinweis darauf sein, dass die Testfälle möglicherweise fehlerhaft sind und keine Aussagekraft haben.

Original: if(TRUE) → Mutant: if(FALSE)

Stress-Test

Eine andere Bezeichnung für Stresstests sind Penetrationstests, die besonders auf die Performance einer Anwendung abgesehen haben. Damit kann man also sicherstellen, dass die Web-App beispielsweise mit einer definierten Anzahl an Zugriffen zurechtkommt.

Natürlich sind noch viele andere hilfreiche Funktionen vorhanden. So kann man Tests beispielsweise gruppieren und die Gruppen können dann einzeln aufgerufen werden.

// definition
pest()->extend(TestCase::class)
->group('feature')
->in('Feature');

// calling
php ./vendor/bin/pest --group=feature

Für alle diejenigen, die nicht mit dem Lavarel Framework arbeiten und dennoch nicht auf das Testen in PHP mit PEST verzichten möchten, können das PEST Framework auch so in ihre Anwendung einbauen. Dazu muss lediglich in der Composer Projektkonfiguration PEST als entsprechende Entwicklungsabhängigkeit definiert werden. Anschließend kann im Wurzelverzeichnis des Projektes das Initial Test Setup angestoßen werden.

php ./vendor/bin/pest --init

Wie wir sehen konnten, sind allein die hier kurz vorgestellten Optionen sehr mächtig. Die offizielle Dokumentation von PEST ist auch sehr ausführlich und sollte grundsätzlich die erste Anlaufstelle sein. In diesem Artikel ging es mir vor allem darum, die Einstiegshürden für testgetriebene Entwicklung in PHP zu minimieren. Denn auch PHP bietet mittlerweile ein gutes Füllhorn an Möglichkeiten, sehr effizient und zuverlässig kommerzielle Softwareprojekte umzusetzen.

Ressourcen

Schreibe einen Kommentar