Unit-Tests sind bei der Softwareentwicklung von entscheidender Bedeutung. Sie stellen sicher, dass die Komponenten deiner Anwendung isoliert voneinander wie erwartet funktionieren. Indem du Tests für bestimmte Codeeinheiten schreibst, kannst du Fehler früh in der Entwicklung erkennen und beheben, was zu einer zuverlässigeren und stabileren Software führt.

In einer CI/CD-Pipeline (Continuous Integration/Continuous Delivery) kannst du diese Tests automatisch ausführen lassen, nachdem du Änderungen an der Codebasis vorgenommen hast. So wird sichergestellt, dass der neue Code keine Fehler enthält oder bestehende Funktionen beschädigt.

Dieser Artikel zeigt, wie wichtig Unit-Tests in Laravel-Anwendungen sind. Er beschreibt, wie du Unit-Tests für eine Laravel-Anwendung schreibst, die über den Anwendungs-Hosting-Dienst von Kinsta bereitgestellt wird.

Einführung in PHPUnit

PHPUnit ist ein weit verbreitetes Testframework im PHP-Ökosystem, das für Unit-Tests entwickelt wurde. Es verfügt über eine Reihe von robusten Tools zum Erstellen und Ausführen von Tests und ist damit eine wichtige Ressource, um die Zuverlässigkeit und Qualität deiner Codebasis sicherzustellen.

Laravel unterstützt das Testen mit PHPUnit und wird mit praktischen Hilfsmethoden geliefert, mit denen du deine Anwendung testen kannst.

Die Einrichtung von PHPUnit in einem Laravel-Projekt erfordert nur eine minimale Konfiguration. Laravel bietet eine vorkonfigurierte Testumgebung, einschließlich einer phpunit.xml-Datei und einem eigenen Testverzeichnis für deine Testdateien.

Alternativ kannst du die phpunit.xml-Datei anpassen, um eigene Optionen für eine maßgeschneiderte Testumgebung zu definieren. Anstelle der . env-Datei kannst du auch eine .env.testing-Umgebungsdatei im Stammordner des Projekts erstellen.

Standard-Testlayout in Laravel

Laravel bietet ein strukturiertes Standardverzeichnislayout. Das Hauptverzeichnis deines Laravel-Projekts enthält ein Verzeichnis tests mit den Unterverzeichnissen Feature und Unit . Dieses Layout macht es einfach, verschiedene Testtypen zu trennen und eine saubere und organisierte Testumgebung zu erhalten.

Die Datei phpunit.xml in einem Laravel-Projekt ist wichtig, um den Testprozess zu steuern, die Konsistenz der Testläufe zu gewährleisten und das Verhalten von PHPUnit an die Projektanforderungen anzupassen. Hier kannst du festlegen, wie die Tests ausgeführt werden sollen, einschließlich der Definition der Testsuiten, der Festlegung der Testumgebung und der Einrichtung von Datenbankverbindungen.

In dieser Datei wird auch festgelegt, dass Session-, Cache- und E-Mail-Daten auf den Array-Treiber gesetzt werden, um sicherzustellen, dass beim Ausführen von Tests keine Daten in Session, Cache oder E-Mail erhalten bleiben.

Du kannst mehrere Arten von Tests für deine Laravel-Anwendung durchführen:

  • Unit-Tests – konzentrieren sich auf einzelne Komponenten deines Codes, z. B. Klassen, Methoden und Funktionen. Diese Tests bleiben von der Laravel-Anwendung isoliert und überprüfen, ob bestimmte Codeeinheiten wie erwartet funktionieren. Beachte, dass Tests, die im Verzeichnis tests/Unit definiert sind, die Laravel-Anwendung nicht booten, d.h. sie können nicht auf die Datenbank oder andere Dienste des Frameworks zugreifen.
  • Feature-Tests – überprüfen die allgemeine Funktionalität deiner Anwendung. Diese Tests simulieren HTTP-Anfragen und -Antworten und ermöglichen es dir, Routen, Controller und die Integration verschiedener Komponenten zu testen. Mit Funktionstests kannst du sicherstellen, dass die verschiedenen Teile deiner Anwendung wie erwartet zusammenarbeiten.
  • Browser-Tests – gehen noch weiter, indem sie Browser-Interaktionen automatisieren. Die Tests verwenden Laravel Dusk, ein Browser-Automatisierungs- und Testwerkzeug, um Benutzerinteraktionen zu simulieren, z. B. das Ausfüllen von Formularen und das Anklicken von Schaltflächen. Browsertests sind wichtig, um das Verhalten deiner Anwendung und das Nutzererlebnis in realen Browsern zu überprüfen.

Konzepte der testgetriebenen Entwicklung

Testgetriebene Entwicklung (TDD) ist ein Softwareentwicklungsansatz, bei dem das Testen im Vordergrund steht, bevor der Code implementiert wird. Dieser Ansatz folgt einem Prozess, der als Rot-Grün-Refactor-Zyklus bekannt ist.

Der testgetriebene Entwicklungszyklus zeigt den Rot-Grün-Refactor
Der testgetriebene Entwicklungszyklus zeigt den Rot-Grün-Refactor

Hier ist eine Erklärung dieses Zyklus:

  • Rote Phase – Schreibe einen neuen Test, um die Funktionalität zu definieren, oder eine Verbesserung eines bestehenden Tests, bevor du den eigentlichen Code implementierst. Der Test sollte fehlschlagen (was „rot“ bedeutet), weil es keinen entsprechenden Code gibt, der ihn erfolgreich macht.
  • Grüne Phase – Schreibe gerade so viel Code, dass der fehlgeschlagene Test bestanden werden kann, sodass er von rot auf grün wechselt. Der Code wird nicht optimal sein, aber er erfüllt die Anforderungen des entsprechenden Testfalls.
  • Refactor-Phase – Refactoriere den Code, um seine Lesbarkeit, Wartbarkeit und Leistung zu verbessern, ohne sein Verhalten zu verändern. In dieser Phase kannst du bequem Änderungen am Code vornehmen, ohne dir Sorgen über Regressionsprobleme machen zu müssen, da die vorhandenen Testfälle diese abfangen.

TDD hat mehrere Vorteile:

  • Frühzeitiges Erkennen von Fehlern – TDD hilft, Fehler schon früh im Entwicklungsprozess zu erkennen, wodurch die Kosten und der Zeitaufwand für die Behebung von Problemen im späteren Verlauf des Entwicklungszyklus reduziert werden.
  • Besseres Design – TDD fördert modularen und lose gekoppelten Code für ein besseres Softwaredesign. Es ermutigt dich, über die Schnittstellen und das Zusammenspiel der Komponenten nachzudenken, bevor du sie implementierst.
  • Vertrauen in das Refactoring – Du kannst den Code getrost refactorisieren, weil du weißt, dass die vorhandenen Tests alle Regressionen, die beim Refactoring eingeführt werden, schnell identifizieren.
  • Lebendige Dokumentation – Testfälle dienen als lebendige Dokumentation, indem sie Beispiele dafür liefern, wie sich der Code verhalten sollte. Diese Dokumentation ist immer auf dem neuesten Stand, da fehlgeschlagene Tests auf Probleme im Code hinweisen.

Bei der Entwicklung von Laravel wendest du die TDD-Prinzipien an, indem du Tests für Komponenten wie Controller, Modelle und Dienste schreibst, bevor du sie implementierst.

Die Testumgebung von Laravel, zu der auch PHPUnit gehört, bietet praktische Methoden und Assertions, um TDD zu erleichtern und sicherzustellen, dass du aussagekräftige Tests erstellen und den Red-Green-Refactor-Zyklus effektiv einhalten kannst.

Grundlegende Beispiele für Unit-Tests

In diesem Abschnitt wird erklärt, wie du einen einfachen Test schreibst, um die Funktionalität deines Modells zu überprüfen.

Voraussetzungen

Um mitzumachen, brauchst du Folgendes:

  • Erfülle die Voraussetzungen, die im Laravel-Blog-Leitfaden aufgeführt sind.
  • Eine Laravel-Anwendung. Dieses Tutorial verwendet die Anwendung, die in der oben verlinkten Anleitung erstellt wurde. Du kannst sie lesen und die Blog-Anwendung erstellen, aber wenn du nur den Quellcode brauchst, um die Tests zu implementieren, befolge die folgenden Schritte.
  • Xdebug ist installiert und mit aktiviertem Abdeckungsmodus konfiguriert.

Richte das Projekt ein

  1. Führe diesen Befehl in einem Terminalfenster aus, um das Projekt zu klonen.
    git clone https://github.com/VirtuaCreative/kinsta-laravel-blog.git
  2. Wechsle in den Projektordner und führe den Befehl composer install aus, um die Projektabhängigkeiten zu installieren.
  3. Benenne die Datei env.example in .env um.
  4. Führe den Befehl php artisan key:generate aus, um einen Anwendungsschlüssel zu generieren.

Tests erstellen und ausführen

Stelle zunächst sicher, dass du den Projektcode auf deinem Rechner hast. Das Modell, das du testen wirst, ist das Modell Post, das in der Datei app/Http/Models/Post.php definiert ist. Dieses Modell umfasst mehrere ausfüllbare Attribute, wie title, description und image.

Deine Aufgabe besteht darin, einfache Unit-Tests für dieses Modell zu erstellen. Ein Test prüft, ob die Attribute richtig gesetzt sind, während ein anderer die Massenzuweisung prüft, indem er versucht, ein nicht ausfüllbares Attribut zuzuweisen.

  1. Führe den Befehl php artisan make:test PostModelFunctionalityTest --unit aus, um einen neuen Testfall zu erstellen. Die Option --unit legt fest, dass es sich um einen Unit-Test handelt und speichert ihn im Verzeichnis tests/Unit .
  2. Öffne die Datei tests/Unit/PostModelFunctionalityTest.php und ersetze die Funktion test_example durch diesen Code:
    public function test_attributes_are_set_correctly()
       {
           // create a new post instance with attributes
           $post = new Post([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
           ]);
    
           // check if you set the attributes correctly
           $this->assertEquals('Sample Post Title', $post->title);
           $this->assertEquals('Sample Post Description', $post->description);
           $this->assertEquals('sample_image.jpg', $post->image);
       }
    
       public function test_non_fillable_attributes_are_not_set()
       {
           // Attempt to create a post with additional attributes (non-fillable)
           $post = new Post([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
               'author' => 'John Doe',
           ]);
    
           // check that the non-fillable attribute is not set on the post instance
           $this->assertArrayNotHasKey('author', $post->getAttributes());
       }

    Dieser Code definiert zwei Testmethoden.

    Die erste erstellt eine Instanz von Post mit den angegebenen Attributen und überprüft mit der Assertion-Methode assertEquals, ob du die Attribute title, description und image richtig gesetzt hast.

    Die zweite Methode versucht, eine Post Instanz mit einem zusätzlichen, nicht ausfüllbaren Attribut (author) zu erstellen und bestätigt mit der assertArrayNotHasKey Assertion-Methode, dass dieses Attribut in der Modellinstanz nicht gesetzt ist.

  3. Stelle sicher, dass du die folgende use Anweisung in der gleichen Datei hinzufügst:
    use App\Models\Post;
  4. Führe den Befehl php artisan config:clear aus, um den Konfigurationscache zu löschen.
  5. Um diese Tests durchzuführen, führe den folgenden Befehl aus:
    php artisan test tests/Unit/PostModelFunctionalityTest.php

    Alle Tests sollten erfolgreich sein und das Terminal sollte die Ergebnisse und die Gesamtzeit für die Ausführung der Tests anzeigen.

Tests debuggen

Wenn Tests fehlschlagen, kannst du sie mit den folgenden Schritten debuggen:

  1. Sieh dir die Fehlermeldung im Terminal an. Laravel liefert detaillierte Fehlermeldungen, die das Problem genau beschreiben. Lies die Fehlermeldung sorgfältig durch, um zu verstehen, warum der Test fehlgeschlagen ist.
  2. Überprüfe die Tests und den Code, den du testest, um Unstimmigkeiten festzustellen.
  3. Vergewissere dich, dass du die für den Test erforderlichen Daten und Abhängigkeiten richtig eingerichtet hast.
  4. Verwende Debugging-Tools wie die dd() -Funktion von Laravel, um Variablen und Daten an bestimmten Stellen deines Testcodes zu überprüfen.
  5. Sobald du das Problem erkannt hast, nimmst du die notwendigen Änderungen vor und führst die Tests erneut durch, bis sie erfolgreich sind.

Tests und Datenbanken

Laravel bietet eine bequeme Möglichkeit, eine Testumgebung mit einer In-Memory-SQLite-Datenbank einzurichten, die schnell ist und keine Daten zwischen den Testläufen speichert. Um die Testdatenbankumgebung zu konfigurieren und Tests zu schreiben, die mit der Datenbank interagieren, befolge die folgenden Schritte:

  1. Öffne die Datei phpunit.xml und kommentiere die folgenden Codezeilen:
    <env name="DB_CONNECTION" value="sqlite"/>
    <env name="DB_DATABASE" value=":memory:"/>
  2. Führe den Befehl php artisan make:test PostCreationTest --unit aus, um einen neuen Testfall zu erstellen.
  3. Öffne die Datei tests/Unit/PostCreationTest.php und ersetze die Methode test_example durch den unten stehenden Code:
    public function testPostCreation()
       {
           // Create a new post and save it to the database
           $post = Post::create([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
           ]);
    
           // Retrieve the post from the database and assert its existence
           $createdPost = Post::find($post->id);
           $this->assertNotNull($createdPost);
           $this->assertEquals('Sample Post Title', $createdPost->title);
       }
  4. Stelle sicher, dass du die folgende use Anweisung hinzufügst:
    use App\Models\Post;

    Derzeit erweitert die Klasse PostCreationTest die Basisklasse PHPUnitFrameworkTestCase. Die Basisklasse wird üblicherweise für Unit-Tests verwendet, wenn du direkt mit PHPUnit außerhalb von Laravel arbeitest oder wenn du Tests für eine Komponente schreibst, die nicht eng mit Laravel gekoppelt ist. Da du jedoch auf die Datenbank zugreifen musst, musst du die Klasse PostCreationTest ändern, um die Klasse TestsTestCase zu erweitern.

    Die letztere Klasse passt die Klasse PHPUnitFrameworkTestCase an Laravel-Anwendungen an. Sie bietet zusätzliche Funktionen und Laravel-spezifische Einstellungen, wie z. B. das Datenbank-Seeding und die Konfiguration der Testumgebung.

  5. Ersetze die Anweisung use PHPUnitFrameworkTestCase; durch use TestsTestCase;. Vergiss nicht, dass du die Testumgebung so eingestellt hast, dass sie eine In-Memory-SQLite-Datenbank verwendet. Daher musst du die Datenbank migrieren, bevor du die Tests durchführst. Verwende dazu den Trait IlluminateFoundationTestingRefreshDatabase. Dieser Trait migriert die Datenbank, wenn das Schema nicht auf dem neuesten Stand ist, und setzt die Datenbank nach jedem Test zurück, um sicherzustellen, dass die Daten des vorherigen Tests die nachfolgenden Tests nicht beeinträchtigen.
  6. Füge die folgende Anweisung use in die Datei tests/Unit/PostCreationTest.php ein, um diesen Trait in deinen Code einzubinden:
    use Illuminate\Foundation\Testing\RefreshDatabase;
  7. Als Nächstes fügst du die folgende Codezeile vor der Methode testPostCreation ein:
    use RefreshDatabase;
  8. Führe den Befehl php artisan config:clear aus, um den Konfigurationscache zu löschen.
  9. Um diesen Test durchzuführen, führe den folgenden Befehl aus:
    php artisan test tests/Unit/PostCreationTest.php

    Die Tests sollten erfolgreich sein und das Terminal sollte die Testergebnisse und die gesamte Testzeit anzeigen.

Funktionstests

Während Unit-Tests einzelne Anwendungskomponenten isoliert prüfen, überprüfen Feature-Tests größere Teile des Codes, z. B. das Zusammenspiel mehrerer Objekte. Funktionstests sind aus mehreren Gründen wichtig:

  1. End-to-End-Validierung – Bestätigt, dass das gesamte Feature nahtlos funktioniert, einschließlich der Interaktionen zwischen den verschiedenen Komponenten wie Controllern, Modellen, Ansichten und sogar der Datenbank.
  2. End-to-End-Tests – Sie decken den gesamten Benutzerfluss von der ersten Anfrage bis zur letzten Antwort ab und können so Probleme aufdecken, die bei Unit-Tests möglicherweise übersehen werden. Diese Fähigkeit macht sie wertvoll für das Testen von User Journeys und komplexen Szenarien.
  3. Sicherstellung des Nutzererlebnisses – Ahmt die Nutzerinteraktionen nach und hilft dabei, ein konsistentes Nutzererlebnis sicherzustellen und zu prüfen, ob die Funktion wie beabsichtigt funktioniert.
  4. Regressionserkennung – Findet Regressionen und Änderungen, die den Code aufbrechen, wenn neuer Code eingeführt wird. Wenn ein bestehendes Feature in einem Feature-Test fehlschlägt, ist das ein Zeichen dafür, dass etwas nicht funktioniert.

Erstelle nun einen Funktionstest für die PostController in der Datei app/Http/Controllers/PostController.php. Du konzentrierst dich auf die Methode store, die Validierung der eingehenden Daten und die Erstellung und Speicherung von Posts in der Datenbank.

Der Test simuliert einen Benutzer, der einen neuen Beitrag über eine Weboberfläche erstellt. Dabei wird sichergestellt, dass der Code den Beitrag in der Datenbank speichert und den Benutzer nach der Erstellung zur Seite Beitragsindex weiterleitet. Befolge dazu die folgenden Schritte:

  1. Führe den Befehl php artisan make:test PostControllerTest aus, um einen neuen Testfall im Verzeichnis tests/Features zu erstellen.
  2. Öffne die Datei tests/Feature/PostControllerTest.php und ersetze die Methode test_example durch diesen Code:
    use RefreshDatabase; // Refresh the database after each test
    
       public function test_create_post()
       {
           // Simulate a user creating a new post through the web interface
           $response = $this->post(route('posts.store'), [
               'title' => 'New Post Title',
               'description' => 'New Post Description',
               'image' => $this->create_test_image(),
           ]);
    
           // Assert that the post is successfully stored in the database
           $this->assertCount(1, Post::all());
    
           // Assert that the user is redirected to the Posts Index page after post creation
           $response->assertRedirect(route('posts.index'));
       }
    
       // Helper function to create a test image for the post
       private function create_test_image()
       {
           // Create a mock image file using Laravel's UploadedFile class
           $file = UploadedFile::fake()->image('test_image.jpg');
    
           // Return the path to the temporary image file
           return $file;
       }

    Die Funktion test_create_post simuliert einen Benutzer, der einen neuen Beitrag erstellt, indem er eine POST -Anfrage an die posts.store -Route mit bestimmten Attributen stellt, einschließlich eines Mock-Images, das mit der Klasse UploadedFile von Laravel generiert wurde.

    Der Test bestätigt, dass der Code den Beitrag erfolgreich in der Datenbank gespeichert hat, indem er den Zähler von Post::all() überprüft. Er verifiziert, dass der Code den Benutzer nach der Erstellung des Beitrags auf die Beitragsindexseite weiterleitet.

    Dieser Test stellt sicher, dass die Beitragserstellung funktioniert und die Anwendung die Datenbankinteraktionen und Weiterleitungen nach der Beitragserstellung korrekt verarbeitet.

  3. Füge die folgenden use Anweisungen in dieselbe Datei ein:
    use App\Models\Post;
    use Illuminate\Http\UploadedFile;
  4. Führe den Befehl php artisan config:clear aus, um den Konfigurations-Cache zu löschen.
  5. Um diesen Test durchzuführen, führe diesen Befehl aus:
    php artisan test tests/Feature/PostControllerTest.php

    Der Test sollte erfolgreich sein und das Terminal sollte die Testergebnisse und die Gesamtzeit für die Durchführung des Tests anzeigen.

Bestätige die Testabdeckung

Die Testabdeckung gibt an, wie viel von der Codebasis deine Unit-, Feature- oder Browsertests überprüfen, ausgedrückt in Prozent. Sie hilft dir, ungetestete Bereiche in deiner Codebasis und unzureichend getestete Bereiche zu identifizieren, die möglicherweise Fehler enthalten.

Tools wie die Codeabdeckungsfunktion von PHPUnit und der integrierte Abdeckungsbericht von Laravel erstellen Berichte, die zeigen, welche Teile deiner Codebasis von deinen Tests abgedeckt werden. Dieser Prozess liefert wichtige Informationen über die Qualität deiner Tests und hilft dir, dich auf Bereiche zu konzentrieren, die zusätzliche Tests erfordern.

Einen Bericht generieren

  1. Lösche die Dateien tests/Feature/ExampleTest.php und tests/Unit/ExampleTest.php, da du sie nicht verändert hast und sie Fehler verursachen könnten.
  2. Führe den Befehl php artisan test --coverage in einem Terminalfenster aus. Du solltest eine Ausgabe wie die folgende erhalten:
    Bildschirmfoto, das die Ausführung des Befehls php artisan test --coverage zeigt. Es zeigt die Gesamtzahl der Tests, die bestanden wurden, und die Zeit für die Ausführung der Ergebnisse. Er listet außerdem jede Komponente in deiner Codebasis und ihren prozentualen Anteil an der Codeabdeckung auf.
    Ausführen des Befehls php artisan test –coverage.

    Der Codeabdeckungsbericht zeigt die Testergebnisse, die Gesamtzahl der bestandenen Tests und die Zeit für die Ausführung der Ergebnisse. Er listet außerdem jede Komponente deiner Codebasis und ihren prozentualen Anteil an der Codeabdeckung auf. Die Prozentzahlen geben den Anteil des Codes an, den deine Tests abdecken.

    Zum Beispiel hat Models/Post eine 100%ige Abdeckung, was bedeutet, dass alle Methoden und Codezeilen des Modells abgedeckt sind. Der Codeabdeckungsbericht zeigt auch die Gesamtabdeckung an – die gesamte Codeabdeckung für die gesamte Codebasis. In diesem Fall decken die Tests nur 65,3 % des Codes ab.

  3. Um einen Schwellenwert für die Mindestabdeckung festzulegen, führe den Befehl php artisan test --coverage --min=85 aus. Dieser Befehl legt eine Mindestabdeckung von 85% fest. Du solltest die folgende Ausgabe erhalten:
    Der Bildschirmausschnitt zeigt den Abdeckungsbericht mit einem Mindestschwellenwert von 85%, der besagt, dass der Test nicht bestanden wurde, weil er den Mindestschwellenwert nicht erreicht hat.
    Test mit einer Mindestabdeckung von 85%.

    Die Testsuiten schlagen fehl, weil der Code den festgelegten Mindestschwellenwert von 85% nicht erreicht.

    Obwohl eine höhere Codeabdeckung – oft 100% – das Ziel ist, ist es wichtiger, die kritischen und komplexen Teile deiner Anwendung gründlich zu testen.

Zusammenfassung

Wenn du dich an die in diesem Artikel beschriebenen Best Practices hältst, wie z. B. das Schreiben aussagekräftiger und umfassender Tests, die Einhaltung des Rot-Grün-Refactor-Zyklus in TDD und die Nutzung der Testfunktionen von Laravel und PHPUnit, kannst du robuste und qualitativ hochwertige Anwendungen erstellen.

Außerdem hast du die Möglichkeit, deine Laravel-Anwendung mit der schnellen, sicheren und zuverlässigen Infrastruktur von Kinsta zu hosten. Außerdem kannst du die Kinsta-API nutzen, um über Plattformen wie GitHub Actions, CircleCI und andere Deployments innerhalb deiner CI/CD-Pipelines zu initiieren.

Jeremy Holcombe Kinsta

Content & Marketing Editor bei Kinsta, WordPress Web Developer und Content Writer. Außerhalb von WordPress genieße ich den Strand, Golf und Filme. Außerdem habe ich Probleme mit großen Menschen ;).