Einleitung: Warum ist Testen in PHP notwendig?
\n\nEntwicklung in PHP ohne Tests ist wie ein Haus ohne Fundament zu bauen. Früher oder später wird die Anwendung abstürzen und die Benutzer sehen einen „weißen Bildschirm des Todes“. Das Testen von Code ist kein Luxus, sondern eine Notwendigkeit für jedes ernsthafte Projekt. Es ermöglicht:
\n- \n
- Fehler frühzeitig zu erkennen – bevor der Code in die Produktion gelangt. \n
- Refactoring zu vereinfachen – Sie können die Architektur bedenkenlos ändern, da die Tests Regressionen abfangen. \n
- Selbstdokumentierenden Code zu erstellen – Tests dienen als lebendige Dokumentation dafür, wie das System funktionieren soll. \n
- Das Vertrauen des Teams zu stärken – jeder neue Commit wird automatisch überprüft. \n
In PHP gibt es drei Hauptebenen des Testens: Unit (Modultests), Integration (Integrationstests) und E2E (End-to-End-Tests). In diesem Artikel werden wir jede dieser Ebenen anhand praktischer Beispiele durchgehen, von einfachen Assertions bis hin zu vollwertigen Browsertests.
\n\n1. Unit-Tests: Die „Bausteine“ der Anwendung testen
\n\nWas ist ein Unit-Test?
\nEin Unit-Test überprüft die Funktionsweise eines kleinen Codeblocks – in der Regel einer einzelnen Methode oder Funktion – in vollständiger Isolation von externen Abhängigkeiten (Datenbank, API, Dateisystem).
\n\nWerkzeuge für PHP
\n- \n
- PHPUnit – der De-facto-Standard. Wird in Laravel, Symfony und vielen CMS verwendet. \n
- Pest – eine moderne Alternative mit prägnanterer Syntax, die auf PHPUnit aufbaut. \n
Beispiel: Einen Rabattrechner testen
\nAngenommen, wir haben eine einfache Klasse DiscountCalculator:
<?php\n\nclass DiscountCalculator\n{\n public function calculate(float $price, float $percent): float\n {\n if ($percent < 0 || $percent > 100) {\n throw new \\InvalidArgumentException('Der Prozentsatz muss zwischen 0 und 100 liegen');\n }\n return $price - ($price * $percent / 100);\n }\n}\n\n\nJetzt schreiben wir einen Unit-Test mit PHPUnit:
\n\n<?php\n\nuse PHPUnit\\Framework\\TestCase;\n\nclass DiscountCalculatorTest extends TestCase\n{\n private DiscountCalculator $calculator;\n\n protected function setUp(): void\n {\n $this->calculator = new DiscountCalculator();\n }\n\n public function testCalculateWithZeroPercent(): void\n {\n $result = $this->calculator->calculate(100, 0);\n $this->assertEquals(100, $result);\n }\n\n public function testCalculateWithFiftyPercent(): void\n {\n $result = $this->calculator->calculate(200, 50);\n $this->assertEquals(100, $result);\n }\n\n public function testCalculateThrowsExceptionOnInvalidPercent(): void\n {\n $this->expectException(\\InvalidArgumentException::class);\n $this->calculator->calculate(100, 150);\n }\n}\n\n\nWichtige Punkte:
\n- \n
- Jede Methode testet nur ein Szenario. \n
- Wir greifen nicht auf die Datenbank oder externe Dienste zu. \n
- Wir verwenden
setUp()zur Initialisierung gemeinsamer Objekte. \n
Bewährte Praktiken für Unit-Tests
\n- \n
- Befolgen Sie das FIRST-Prinzip: Fast, Isolated, Repeatable, Self-validating, Timely. \n
- Benennen Sie Tests nach dem Schema „test_Aktion_erwartetesErgebnis“. \n
- Vermeiden Sie Logik in Tests – keine if/switch/Schleifen. \n
2. Integrationstests: Die Interaktion von Komponenten überprüfen
\n\nWas ist der Unterschied zu Unit-Tests?
\nIntegrationstests überprüfen, wie verschiedene Teile des Systems zusammenarbeiten. Zum Beispiel, wie Ihr Dienst mit einer echten Datenbank, einem Cache oder einer externen API interagiert.
\n\nBeispiel: Ein Repository mit Datenbank testen (Laravel + SQLite)
\nAngenommen, wir haben ein