Wie ein Boss in Laravel testen Modelle

Wenn Sie wissen wollen, warum Tests von Vorteil sind, ist dies nicht der Artikel für Sie. Im Verlauf dieses Tutorials gehe ich davon aus, dass Sie die Vorteile bereits verstehen und hoffen, dass Sie lernen, wie Sie Ihre Tests in Laravel 4 am besten schreiben und organisieren.

Die Version 4 von Laravel bietet im Vergleich zur vorherigen Version gravierende Verbesserungen in Bezug auf das Testen. Dies ist der erste Artikel einer Serie, in der beschrieben wird, wie Tests für Laravel 4-Anwendungen geschrieben werden. Wir beginnen die Serie mit Modellversuchen.


Konfiguration

In-Memory-Datenbank

Wenn Sie keine Rohabfragen in Ihrer Datenbank ausführen, ermöglicht Laravel, dass Ihre Anwendung datenbankunabhängig bleibt. Mit einem einfachen Treiberwechsel kann Ihre Anwendung jetzt mit anderen DBMS (MySQL, PostgreSQL, SQLite usw.) arbeiten. Unter den Standardoptionen bietet SQLite eine besondere, jedoch sehr nützliche Funktion: In-Memory-Datenbanken.

Mit Sqlite können wir die Datenbankverbindung auf setzen :Erinnerung:, Dies wird unsere Tests drastisch beschleunigen, da die Datenbank nicht auf der Festplatte vorhanden ist. Darüber hinaus wird die Produktions- / Entwicklungsdatenbank niemals mit übrig gebliebenen Testdaten gefüllt, da die Verbindung besteht, :Erinnerung:, beginnt immer mit einer leeren Datenbank.

Kurz gesagt: Eine In-Memory-Datenbank ermöglicht schnelle und saubere Tests.

Innerhalb des app / config / testing Erstellen Sie eine neue Datei mit dem Namen database.php, und fülle es mit folgendem Inhalt:

// app / config / testing / database.php  'sqlite', 'connections' => array ('sqlite' => array ('driver' => 'sqlite', 'database' => ': memory:', 'prefix' => "),)));

Die Tatsache, dass database.php wird in die Konfiguration eingefügt testen Verzeichnis bedeutet, dass diese Einstellungen nur in einer Testumgebung verwendet werden (die Laravel automatisch einstellt). Wenn auf Ihre Anwendung normalerweise zugegriffen wird, wird die In-Memory-Datenbank nicht verwendet.

Vor dem Ausführen von Tests

Da die In-Memory-Datenbank beim Herstellen einer Verbindung immer leer ist, ist dies wichtig Wandern die Datenbank vor jedem Test. Öffnen Sie dazu app / tests / TestCase.php und fügen Sie die folgende Methode am Ende der Klasse hinzu:

/ ** * Migriert die Datenbank und setzt den Mailer auf "Pretend". * Dadurch werden die Tests schnell ausgeführt. * * / private function preparForTests () Artisan :: call ('migrate'); Mail :: pretend (true); 

Beachten Sie das Konfiguration() Die Methode wird vor jedem Test von PHPUnit ausgeführt.

Diese Methode bereitet die Datenbank vor und ändert den Status von Laravel Mailer Klasse zu so tun als ob. Auf diese Weise sendet der Mailer beim Ausführen von Tests keine echten E-Mails. Stattdessen werden die "gesendeten" Nachrichten protokolliert.

Zu finalisieren app / tests / TestCase.php, Anruf prepareForTests () innerhalb der PHPUnit Konfiguration() Methode, die vor jedem Test ausgeführt wird.

Vergiss das nicht parent :: setUp (), da wir die Methode der Elternklasse überschreiben.

/ ** * Standardvorbereitung für jeden Test * * / public function setUp () parent :: setUp (); // Vergiss das nicht! $ this-> prepareForTests (); 

An diesem Punkt, app / tests / TestCase.php sollte wie der folgende Code aussehen. Erinnere dich daran createApplication wird automatisch von Laravel erstellt. Sie brauchen sich keine Sorgen zu machen.

// app / tests / TestCase.php prepareForTests ();  / ** * Erstellt die Anwendung. * * @return Symfony \ Component \ HttpKernel \ HttpKernelInterface * / public function createApplication () $ unitTesting = true; $ testEnvironment = 'testing'; Rückkehr erforderlich __DIR __. '/… /… /start.php';  / ** * Migriert die Datenbank und setzt den Mailer auf "Pretend". * Dadurch werden die Tests schnell ausgeführt. * / private function preparForTests () Artisan :: call ('migrate'); Mail :: pretend (true); 

Um unsere Tests zu schreiben, erweitern Sie einfach Testfall, und die Datenbank wird vor jedem Test initialisiert und migriert.


Die Tests

Es ist richtig zu sagen, dass wir in diesem Artikel das nicht verfolgen werden TDD verarbeiten. Das Thema hier ist didaktisch, mit dem Ziel zu zeigen, wie die Tests geschrieben werden können. Aus diesem Grund habe ich mich dazu entschieden, zuerst die fraglichen Modelle und dann die zugehörigen Tests zu veröffentlichen. Ich glaube, dass dies ein besserer Weg ist, dieses Tutorial zu veranschaulichen.

Der Kontext dieser Demoanwendung ist ein einfaches Blog / CMS, das Benutzer (Authentifizierung), Posts und statische Seiten (die im Menü angezeigt werden) enthält..

Post-Modell

Bitte beachten Sie, dass das Modell die Klasse Ardent und nicht Eloquent erweitert. Ardent ist ein Paket, das beim Speichern des Modells eine einfache Überprüfung ermöglicht (siehe $ regeln Eigentum).

Als nächstes haben wir die öffentliche statische $ fabrik Array, das das FactoryMuff-Paket nutzt, um die Objekterstellung beim Testen zu unterstützen.

Beide Ardentx und FactoryMuff sind über Packagist und Composer erhältlich.

In unserer Post Modell haben wir eine Beziehung mit der Nutzer Modell, durch die Magie Autor Methode.

Schließlich haben wir eine einfache Methode, die das Datum zurückgibt, formatiert als "Tag Monat Jahr".

// app / models / Post.php  'required', // Post tittle 'slug' => 'required | alpha_dash', // Post-URL 'content' => 'required', // Post-Inhalt (Markdown) 'author_id' => 'required | numerisch', // Author id); / ** * Von FactoryMuff zum Erstellen von Testobjekten verwendetes Array * / public static $ factory = array ('title' => 'string', 'slug' => 'string', 'content' => 'text', 'author_id '=>' factory | User ', // Wird die ID eines existierenden Benutzers sein.); / ** * Gehört zu Benutzer * / public function author () return $ this-> AttractionsTo ('Benutzer', 'author_id');  / ** * Formatiertes Postdatum abrufen * * @return string * / public function postedAt () $ date_obj = $ this-> created_at; if (is_string ($ this-> created_at)) $ date_obj = DateTime :: createFromFormat ('Y-m-d H: i: s', $ date_obj); $ date_obj-> format ('d / m / Y') zurückgeben; 

Post-Tests

Um die Dinge in Ordnung zu halten, habe ich die Klasse bei der Post Modellversuche in app / tests / models / PostTest.php. Wir werden alle Tests einzeln durchgehen.

// app / tests / models / PostTest.php  

Wir erweitern das Testfall Klasse, die für das Testen von PHPUnit in Laravel erforderlich ist. Vergiss auch nicht unsere Vorbereitungen Methode, die vor jedem Test ausgeführt wird.

 public function test_relation_with_author () // Instanziieren, mit Werten füllen, speichern und zurückgeben $ post = FactoryMuff :: create ('Post'); // Dank FactoryMuff hat dieser $ post einen Autor $ this-> assertEquals ($ post-> author_id, $ post-> author-> id); 

Dieser Test ist "optional". Wir testen die Beziehung "Post gehört Nutzer". Der Zweck hier besteht hauptsächlich darin, die Funktionalität von FactoryMuff zu demonstrieren.

Einmal die Post Klasse haben die $ Fabrik statisches Array enthält 'author_id' => 'factory | User' (Beachten Sie den oben gezeigten Quellcode des Modells). FactoryMuff instanziiert einen neuen Nutzer füllt seine Attribute, speichert sie in der Datenbank und gibt schließlich ihre ID an den zurück author_id Attribut in der Post.

Damit dies möglich ist, kann der Nutzer Modell muss eine haben $ Fabrik Array, das auch seine Felder beschreibt.

Beachten Sie, wie Sie auf das zugreifen können Nutzer Beziehung durch $ post-> Autor. Als Beispiel können wir auf das zugreifen $ post-> author-> Benutzername, oder ein beliebiges anderes vorhandenes Benutzerattribut.

Das FactoryMuff-Paket ermöglicht die schnelle Instantiierung von konsistenten Objekten zu Testzwecken, wobei alle erforderlichen Beziehungen berücksichtigt und instanziiert werden. In diesem Fall, wenn wir ein erstellen Post mit FactoryMuff :: create ('Post') das Nutzer wird auch vorbereitet und zur Verfügung gestellt.

 public function test_posted_at () // Instanziieren, mit Werten füllen, speichern und zurückgeben $ post = FactoryMuff :: create ('Post'); // regulärer Ausdruck, der das Muster d / m / Y darstellt $ erwartet = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // True, wenn preg_match das Muster $ Matches = findet (Preg_match ($ erwartet, $ post-> postedAt ()))? wahr falsch; $ this-> assertTrue ($ entspricht); 

Zum Abschluss bestimmen wir, ob die Zeichenfolge von zurückgegeben wird postedAt () Methode folgt dem Format "Tag / Monat / Jahr". Für eine solche Überprüfung wird ein regulärer Ausdruck verwendet, um das Muster zu testen \ d 2 \ / \ d 2 \ / \ d 4 ("2 Zahlen" + "Leiste" + "2 Zahlen" + "Leiste" + "4 Zahlen") gefunden.

Alternativ können wir den assertRegExp-Matcher von PHPUnit verwenden.

An diesem Punkt ist die app / tests / models / PostTest.php Datei ist wie folgt:

// app / tests / models / PostTest.php assertEquals ($ post-> author_id, $ post-> author-> id);  public function test_posted_at () // Instanziieren, mit Werten füllen, speichern und zurückgeben $ post = FactoryMuff :: create ('Post'); // regulärer Ausdruck, der das Muster d / m / Y darstellt $ erwartet = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // True, wenn preg_match das Muster $ Matches = findet (Preg_match ($ erwartet, $ post-> postedAt ()))? wahr falsch; $ this-> assertTrue ($ entspricht); 

PS: Ich habe mich aus Gründen der Lesbarkeit entschieden, den Namen der Tests nicht in CamelCase zu schreiben. PSR-1 verzeihen mir aber testRelationWithAuthor ist nicht so lesbar, wie ich es persönlich bevorzugen würde. Natürlich können Sie den von Ihnen bevorzugten Stil verwenden.

Seitenmodell

Unser CMS benötigt ein Modell zur Darstellung statischer Seiten. Dieses Modell wird wie folgt implementiert:

 'required', // Seitentitel 'slug' => 'required | alpha_dash', // Slug (url) 'content' => 'required', // Inhalt (markdown) 'author_id' => 'required | numerisch' // Autorennummer); / ** * Von FactoryMuff verwendetes Array * / public static $ factory = array ('title' => 'string', 'slug' => 'string', 'content' => 'text', 'author_id' => ' factory | User ', // Wird die ID eines vorhandenen Benutzers sein.); / ** * Gehört zu Benutzer * / public function author () return $ this-> AttractionsTo ('Benutzer', 'author_id');  / ** * Rendert das Menü mit cache * * @return string HTML für Seitenverknüpfungen. * / public static function renderMenu () $ pages = Cache :: rememberForever ('pages_for_menu', function () return Page :: select (array ('title', 'slug')) -> get () -> toArray ();); $ result = "; foreach ($ pages as $ page) $ result. = HTML :: action ('PagesController @ show', $ page ['title'], ['slug' => $ page ['slug']) ]). ' | '; return $ result; / ** * Cache beim Speichern vergessen * / public function afterSave ($ success) if ($ success) Cache :: forget (' pages_for_menu '); / ** * Cache vergessen beim gelöscht * / public function delete () parent :: delete (); Cache :: forget ('pages_for_menu');

Wir können das die statische Methode beobachten, renderMenu (), rendert eine Reihe von Links für alle vorhandenen Seiten. Dieser Wert wird im Cache-Schlüssel gespeichert, 'pages_for_menu'. Auf diese Weise werden in Zukunft Anrufe an renderMenu (), Es ist nicht nötig, die echte Datenbank zu treffen. Dies kann zu erheblichen Verbesserungen der Leistung unserer Anwendung führen.

Wenn jedoch a Seite wird gespeichert oder gelöscht (afterSave () und löschen() Methoden), wird der Wert des Caches gelöscht und bewirkt, dass renderMenu () um den neuen Zustand der Datenbank wiederzugeben. Wenn also der Name einer Seite geändert oder gelöscht wird, wird der Schlüssel 'pages_for_menu' wird aus dem Cache gelöscht. (Cache :: forget ('pages_for_menu');)

HINWEIS: Die Methode, afterSave (), ist über das Ardent-Paket erhältlich. Andernfalls wäre die Implementierung der sparen() Methode, um den Cache zu bereinigen und aufzurufen parent :: save ();

Seitentests

Im: app / tests / models / PageTest.php, Wir schreiben die folgenden Tests:

assertEquals ($ page-> author_id, $ page-> author-> id); 

Wieder einmal haben wir einen "optionalen" Test, um die Beziehung zu bestätigen. Da Beziehungen in der Verantwortung liegen Illuminate \ Database \ Eloquent, Da Laravel bereits eigene Tests durchführt, müssen Sie keinen weiteren Test schreiben, um zu bestätigen, dass dieser Code wie erwartet funktioniert.

 öffentliche Funktion test_render_menu () $ pages = array (); für ($ i = 0; $ i < 4; $i++)  $pages[] = FactoryMuff::create('Page');  $result = Page::renderMenu(); foreach ($pages as $page)  // Check if each page slug(url) is present in the menu rendered. $this->assertGreaterThan (0, strpos ($ result, $ page-> slug));  // Prüfen Sie, ob der Cache geschrieben wurde. $ This-> assertNotNull (Cache :: get ('pages_for_menu')); 

Dies ist einer der wichtigsten Tests für Seite Modell. Zunächst werden vier Seiten im erstellt zum Schleife. Im Anschluss daran das Ergebnis der renderMenu () Anruf wird im gespeichert $ Ergebnis Variable. Diese Variable sollte eine HTML-Zeichenfolge enthalten, die Links zu den vorhandenen Seiten enthält.

Das für jeden Schleife prüft, ob der Slug (URL) jeder Seite in vorhanden ist $ Ergebnis. Dies ist ausreichend, da das genaue Format des HTML-Codes für unsere Anforderungen nicht relevant ist.

Schließlich bestimmen wir, ob der Cache-Schlüssel, pages_for_menu, hat etwas gespeichert. Mit anderen Worten, tat das renderMenu () Aufruf hat tatsächlich einen Wert im Cache gespeichert?

 public function test_clear_cache_after_save () // Ein Testwert wird im Cache gespeichert. Cache :: put ('pages_for_menu', 'avalue', 5); // Dies sollte den Wert im Cache bereinigen. $ Page = FactoryMuff :: create ('Page'); $ this-> assertNull (Cache :: get ('pages_for_menu')); 

Mit diesem Test soll überprüft werden, ob beim Speichern eines neuen Seite, der Cache-Schlüssel 'pages_for_menu' wird geleert. Das FactoryMuff :: create ('Page'); löst schließlich das aus sparen() Methode, so dass das für den Schlüssel ausreichen sollte, 'pages_for_menu', zu klären.

 öffentliche Funktion test_clear_cache_after_delete () $ page = FactoryMuff :: create ('Page'); // Ein Testwert wird im Cache gespeichert. Cache :: put ('pages_for_menu', 'value', 5); // Dies sollte den Wert im Cache leeren. $ Page-> delete (); $ this-> assertNull (Cache :: get ('pages_for_menu')); 

Ähnlich wie beim vorherigen Test bestimmt dieser Test, ob der Schlüssel vorliegt 'pages_for_menu' wird nach dem Löschen von a ordnungsgemäß geleert Seite.

Ihre PageTest.php sollte so aussehen:

assertEquals ($ page-> author_id, $ page-> author-> id);  public function test_render_menu () $ pages = array (); für ($ i = 0; $ i < 4; $i++)  $pages[] = FactoryMuff::create('Page');  $result = Page::renderMenu(); foreach ($pages as $page)  // Check if each page slug(url) is present in the menu rendered. $this->assertGreaterThan (0, strpos ($ result, $ page-> slug));  // Prüfen Sie, ob der Cache geschrieben wurde. $ This-> assertNotNull (Cache :: get ('pages_for_menu'));  public function test_clear_cache_after_save () // Ein Testwert wird im Cache gespeichert. Cache :: put ('pages_for_menu', 'avalue', 5); // Dies sollte den Wert im Cache bereinigen. $ Page = FactoryMuff :: create ('Page'); $ this-> assertNull (Cache :: get ('pages_for_menu'));  public function test_clear_cache_after_delete () $ page = FactoryMuff :: create ('Page'); // Ein Testwert wird im Cache gespeichert. Cache :: put ('pages_for_menu', 'value', 5); // Dies sollte den Wert im Cache leeren. $ Page-> delete (); $ this-> assertNull (Cache :: get ('pages_for_menu')); 

Benutzermodell

Bezogen auf die zuvor vorgestellten Modelle haben wir jetzt die Nutzer. Hier ist der Code für dieses Modell:

 'string', 'email' => 'email', 'password' => '123123', 'password_confirmation' => '123123',); / ** * Hat viele Seiten * / public function pages () return $ this-> hasMany ('Page', 'author_id');  / ** * Hat viele Beiträge * / public function posts () return $ this-> hasMany ('Post', 'author_id'); 

Dieses Modell ist ohne Tests.

Wir können beobachten, dass es hier mit Ausnahme von Beziehungen (die zum Testen hilfreich sein können) keine Methodenimplementierung gibt. Was ist mit der Authentifizierung? Nun, die Verwendung des Confide-Pakets liefert bereits die Implementierung und Tests dafür.

Die Tests für Zizaco \ Confide \ ConfideUser befinden sich in ConfideUserTest.php.

Es ist wichtig, die Verantwortlichkeiten der Klassen zu bestimmen, bevor Sie Ihre Tests schreiben. Testen Sie die Option zu "setze das Passwort zurück" von a Nutzer wäre überflüssig. Dies liegt daran, dass die richtige Verantwortung für diesen Test im Innern liegt Zizaco \ Confide \ ConfideUser; nicht in Nutzer.

Gleiches gilt für Datenvalidierungstests. Da das Paket, Ardent, mit dieser Verantwortung umgeht, wäre es nicht sinnvoll, die Funktionalität erneut zu testen.

Zusamenfassend: Halten Sie Ihre Tests sauber und organisiert. Bestimmen Sie die ordnungsgemäße Verantwortung jeder Klasse und testen Sie nur, was ausschließlich in ihrer Verantwortung liegt.


Fazit

Die Verwendung einer In-Memory-Datenbank ist eine bewährte Methode, um Tests schnell gegen eine Datenbank auszuführen. Dank der Hilfe einiger Pakete wie Ardent, FactoryMuff und Confide können Sie die Menge an Code in Ihren Modellen minimieren, während die Tests sauber und objektiv bleiben.

In der Fortsetzung dieses Artikels werden wir es noch einmal überprüfen Regler testen. Bleib dran!

Da wir uns noch mit Laravel 4 beschäftigen, möchten wir Ihnen das Wesentliche beibringen!