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.
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.
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.
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..
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;
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.phpWir erweitern das
Testfall
Klasse, die für das Testen von PHPUnit in Laravel erforderlich ist. Vergiss auch nicht unsereVorbereitungen
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örtNutzer
". 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 neuenNutzer
füllt seine Attribute, speichert sie in der Datenbank und gibt schließlich ihre ID an den zurückauthor_id
Attribut in derPost
.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
mitFactoryMuff :: create ('Post')
dasNutzer
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 anrenderMenu ()
, 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 ()
undlöschen()
Methoden), wird der Wert des Caches gelöscht und bewirkt, dassrenderMenu ()
um den neuen Zustand der Datenbank wiederzugeben. Wenn also der Name einer Seite geändert oder gelöscht wird, wird derSchlü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 dersparen()
Methode, um den Cache zu bereinigen und aufzurufenparent :: 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 erstelltzum
Schleife. Im Anschluss daran das Ergebnis derrenderMenu ()
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 dasrenderMenu ()
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. DasFactoryMuff :: create ('Page');
löst schließlich das aussparen()
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äß geleertSeite
.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 liegtZizaco \ Confide \ ConfideUser
; nicht inNutzer
.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!