Om du hoppas att lära dig varför testerna är fördelaktiga, så är det inte artikeln för dig. Under denna handledning kommer jag att anta att du redan förstår fördelarna och hoppas kunna lära dig hur man bäst skriver och organiserar dina tester i Laravel 4.
Version 4 av Laravel erbjuder seriösa förbättringar i förhållande till testning, jämfört med tidigare utgåva. Detta är den första artikeln i en serie som kommer att omfatta hur man skriver tester för Laravel 4-applikationer. Vi börjar serien genom att diskutera modelltestning.
Om du inte kör raka frågor i din databas tillåter Laravel att din ansökan förbli databas agnostic. Med en enkel förarbyte kan din applikation nu fungera med andra DBMS (MySQL, PostgreSQL, SQLite, etc.). Bland standardalternativen erbjuder SQLite en märklig men ändå väldigt användbar funktion: databaser i minnet.
Med Sqlite kan vi ställa in databasanslutningen till :minne:
, vilket kommer att drastiskt påskynda våra test, på grund av att databasen inte finns på hårddisken. Dessutom kommer databasen för produktion / utveckling aldrig att fyllas med överlämningsdata, eftersom anslutningen, :minne:
, börjar alltid med en tom databas.
Kort sagt: En in-memory-databas möjliggör snabba och rena test.
Inom app / config / testning
katalog, skapa en ny fil, namngiven database.php
, och fyll i det med följande innehåll:
// app / config / test / database.php 'sqlite', 'connections' => array ('sqlite' => array ('drivrutin' => 'sqlite', 'database' => ': minne:', 'prefix' => ")
Faktumet att database.php
placeras inom konfigurationen testning
katalog betyder att dessa inställningar endast används i en testmiljö (vilken Laravel automatiskt ställer in). Som sådan, när din ansökan är tillgänglig normalt, kommer inte minnesdatabasen att användas.
Eftersom databasen i minnet alltid är tom när en anslutning görs är det viktigt att vandra databasen före varje test. För att göra detta, öppna app / test / TestCase.php
och lägg till följande metod till slutet av klassen:
/ ** * Migrerar databasen och sätter brevaren till "låtsas". * Detta gör att testen körs snabbt. * * / privat funktion prepareForTests () Artisan :: call ('migrate'); Post :: låtsas (true);
Notera
inrätta()
Metoden utförs av PHPUnit före varje test.
Denna metod kommer att förbereda databasen och ändra status för Laravel s mailer
klass till låtsas
. På så sätt skickar Mailer inte något riktigt e-postmeddelande när du kör test. Istället kommer det att logga in de "skickade" meddelandena.
Att slutföra app / test / TestCase.php
, ring upp prepareForTests ()
inom PHPUnit inrätta()
metod som kommer att utföras före varje test.
Glöm inte
förälder :: SETUP ()
, som vi skriver över moderklassens metod.
/ ** * Standardförberedelse för varje test * * / public function setUp () förälder :: setUp (); // Glöm inte detta! $ This-> prepareForTests ();
Vid denna punkt, app / test / TestCase.php
ska se ut som följande kod. Kom ihåg det createApplication
skapas automatiskt av Laravel. Du behöver inte oroa dig för det.
// app / test / TestCase.php prepareForTests (); / ** * Skapar programmet. * * @return Symfony \ Component \ HttpKernel \ HttpKernelInterface * / public function createApplication () $ unitTesting = true; $ testEnvironment = 'testning'; retur kräver __DIR __. '/ ... / ... / start.php'; / ** * Migrerar databasen och sätter brevaren till "låtsas". * Detta gör att testen körs snabbt. * / privat funktion prepareForTests () Artisan :: call ('migrate'); Post :: låtsas (true);
Nu, för att skriva våra test, utvidga vi bara Testfall
, och databasen kommer att initieras och migreras före varje test.
Det är korrekt att säga att vi i denna artikel inte kommer att följa TDD bearbeta. Frågan här är didaktisk, med målet att visa hur testen kan skrivas. På grund av detta valde jag att avslöja modellerna i fråga först och sedan deras relaterade tester. Jag tror att det här är ett bättre sätt att illustrera denna handledning.
Sammanhanget med denna demo-applikation är en enkel blogg / CMS, innehållande användare (autentisering), inlägg och statiska sidor (som visas i menyn).
Observera att modellen utökar klassen Ardent snarare än Eloquent. Ardent är ett paket som gör det enkelt att validera, när man sparar modellen (se $ regler
fast egendom).
Därefter har vi offentlig statisk $ fabrik
array, som utnyttjar FactoryMuff-paketet, för att hjälpa till med att skapa objekt vid testning.
Både Ardentx och FactoryMuff finns tillgängliga via Packagist and Composer.
I vår Posta
modell, vi har ett förhållande till Användare
modell, genom magi författare
metod.
Slutligen har vi en enkel metod som returnerar datumet formaterat som "dag månad år".
// app / modeller / Post.php 'Required', // Posttitel 'slug' => 'krävs' alpha_dash ', // Post Url' content '=>' krävs ', // Inlägg innehåll (Markdown)' author_id '=>' krävs | numeric ', // Författarens id); / ** * Array används av FactoryMuff för att skapa Testobjekt * / offentliga statiska $ factory = array ('title' => 'string', 'slug' => 'string', 'content' => 'text', 'author_id '=>' fabrik | Användare ', // Kommer vara ID för en existerande Användare.); / ** * Hör till användaren * / public function author () return $ this-> belongsTo ('User', 'author_id'); / ** * Få formaterad postdatum * * @returnsträng * / offentlig funktion postedAt () $ date_obj = $ this-> created_at; om (is_string ($ this-> created_at)) $ date_obj = DateTime :: createFromFormat ('Y-m-d H: i: s', $ date_obj); returnera $ date_obj-> format ('d / m / y');
För att hålla saker organiserade har jag lagt klassen med Posta
modell tester i app / tester / modeller / PostTest.php
. Vi går igenom alla tester, en sektion åt gången.
// app / test / modeller / PostTest.phpVi förlänger
Testfall
klass, vilket är ett krav för PHPUnit testning i Laravel. Glöm inte heller vårtprepareTests
metod som löper före varje test.public function test_relation_with_author () // Instantiate, fyll med värden, spara och returnera $ post = FactoryMuff :: skapa ('Post'); // Tack vare FactoryMuff har denna $ post en författare $ this-> assertEquals ($ post-> author_id, $ post-> author-> id);Detta test är en "valfri" en. Vi testar att förhållandet "
Posta
tillhörAnvändare
". Syftet här är mestadels att visa FactoryMuffs funktionalitet.När
Posta
klassen har$ fabrik
statisk matris innehållande'author_id' => 'factory | User'
(notera modellens källkod, visas ovan) FactoryMuff inställa en nyAnvändare
fyller dess attribut, spara i databasen och äntligen returnera dess id tillauthor_id
attribut iPosta
.För att detta ska vara möjligt,
Användare
Modellen måste ha en$ fabrik
array som beskriver dess fält också.Lägg märke till hur du kan komma åt
Användare
relation genom$ Post-> författare
. Som ett exempel kan vi komma åt$ Post-> author-> användarnamn
, eller någon annan befintlig användarattribut.FactoryMuff-paketet möjliggör snabb instansering av konsekventa föremål för att testa, samtidigt som man respekterar och instanserar eventuella nödvändiga relationer. I det här fallet när vi skapar en
Posta
medFactoryMuff :: skapa (Post)
deAnvändare
kommer också att förberedas och ställas till förfogande.allmän funktion test_posted_at () // Instantiate, fyll med värden, spara och returnera $ post = FactoryMuff :: skapa ('Post'); // Regular expression som representerar d / m / Y mönster $ expected = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // True om preg_match hittar mönstret $ matches = (preg_match ($ expected, $ post-> postedAt ()))? sant falskt; $ this-> assertTrue ($ matchningar);För att avsluta bestämmer vi om strängen returneras av
postedAt ()
Metoden följer formatet "dag / månad / år". För en sådan kontroll används ett vanligt uttryck för att testa om mönstret\ D 2 \ / \ d 2 \ / \ d 4
("2 nummer" + "bar" + "2 nummer" + "bar" + "4 siffror") är hittad.Alternativt kan vi använda PHPUnits assertRegExp matcher.
Vid denna tidpunkt
app / tester / modeller / PostTest.php
filen är som följer:// app / test / modeller / PostTest.php assertEquals ($ post-> author_id, $ post-> author-> id); public function test_posted_at () // Instantiate, fyll med värden, spara och returnera $ post = FactoryMuff :: skapa ('Post'); // Regular expression som representerar d / m / Y mönster $ expected = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // True om preg_match hittar mönstret $ matches = (preg_match ($ expected, $ post-> postedAt ()))? sant falskt; $ this-> assertTrue ($ matchningar);PS: Jag valde att inte skriva namnet på testen i CamelCase för läsbarhetsändamål. PSR-1 förlåter mig, men
testRelationWithAuthor
är inte lika läsbar som jag personligen föredrar. Du kan naturligtvis använda den stil du föredrar mest.Sidmodell
Vårt CMS behöver en modell för att representera statiska sidor. Denna modell är implementerad enligt följande:
'Required', // Sidtitel 'slug' => 'krävs' alpha_dash ', // Slug (url)' content '=>' krävs ', // Innehåll (markdown)' author_id '=>' krävs | numeric ' , // Författarens id); / ** * Array används av FactoryMuff * / public static $ factory = array ('title' => 'sträng', 'slug' => 'string', 'content' => 'text', 'author_id' => ' fabriks | Användare ', // Kommer vara ID för en existerande Användare.); / ** * Hör till användaren * / public function author () return $ this-> belongsTo ('User', 'author_id'); / ** * Ger menyn med cachen * * @returnsträngen Html för sidlänkar. * / offentliga statiska funktionen renderMenu () $ pages = Cache :: rememberForever ('pages_for_menu', funktion () return Page :: select (array ('titel', 'slug')) -> get () -> toArray ();); $ result = "; foreach ($ sidor som $ sida) $ result. = HTML :: action ('PagesController @ show', $ page ['title'], ['slug' => $ sida ['slug'] ]). ' | '; returnera $ result; / ** * Glöm cache när den sparas * / public function afterSave ($ success) om ($ success) Cache :: forget (' pages_for_menu '); / ** * Glöm cache när raderad * / public function delete () förälder :: radera (); Cache :: glömma ('pages_for_menu');Vi kan observera att den statiska metoden,
renderMenu ()
, gör ett antal länkar för alla befintliga sidor. Detta värde sparas i cachernyckeln,'Pages_for_menu'
. På så sätt, i framtiden, samtal tillrenderMenu ()
, det blir inte nödvändigt att slå den riktiga databasen. Detta kan ge avsevärda förbättringar av vår applikations prestanda.Om en
Sida
sparas eller raderas (afterSave ()
ochradera()
metoder), kommer cachens värde att rensas, vilket orsakarrenderMenu ()
för att återspegla den nya databasen. Så, om namnet på en sida ändras, eller om den är raderad, sånyckel "pages_for_menu"
rensas från cacheminnet. (Cache :: glömma (pages_for_menu ');
)OBS: Metoden,
afterSave ()
, är tillgängligt via Ardent-paketet. Annars skulle det vara nödvändigt att genomföraspara()
Metod för att rengöra cacheminnet och samtaletförälder :: save ()
;Sidtest
I:
app / tester / modeller / PageTest.php
, vi skriver följande tester:assertEquals ($ page-> author_id, $ page-> author-> id);Återigen har vi ett "valfritt" test för att bekräfta förhållandet. Som relationer är ansvaret för
Illuminate \ Database \ Eloquent
, som redan omfattas av Laravel egna test, behöver vi inte skriva ett annat test för att bekräfta att denna kod fungerar som förväntat.allmän 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)); // Kontrollera om cachen har skrivits $ this-> assertNotNull (Cache :: get ('pages_for_menu'));Detta är en av de viktigaste testerna för
Sida
modell. Först skapas fyra sidor iför
slinga. Efter det, resultatet avrenderMenu ()
samtal lagras i$ result
variabel. Denna variabel ska innehålla en HTML-sträng som innehåller länkar till de befintliga sidorna.De
för varje
loop kontrollerar om släkten (url) på varje sida finns i$ result
. Detta räcker, eftersom HTML-exakt format inte är relevant för våra behov.Slutligen bestämmer vi om cache-nyckeln,
pages_for_menu
, har lagrat något Med andra ord gjorderenderMenu ()
samtal sparas faktiskt värde till cacheminnet?allmän funktion test_clear_cache_after_save () // Ett testvärde sparas i cacheminnet :: put ('pages_for_menu', 'avalue', 5); // Det här ska städa värdet i cachen $ page = FactoryMuff :: skapa ('Sida'); $ This-> assertNull (Cache :: få (pages_for_menu '));Detta test syftar till att verifiera om, när en ny lagras
Sida
, cachernyckeln'Pages_for_menu'
töms. DeFactoryMuff :: skapa (sidan ');
så småningom utlöserspara()
metod, så det borde vara tillräckligt för nyckeln,'Pages_for_menu'
, att rensas.allmän funktion test_clear_cache_after_delete () $ page = FactoryMuff :: skapa ('Sida'); // Ett testvärde sparas i cachemachin :: put ('pages_for_menu', 'value', 5); // Detta bör städa värdet i cachen $ page-> delete (); $ This-> assertNull (Cache :: få (pages_for_menu '));På samma sätt som det föregående testet bestämmer denna om nyckeln
'Pages_for_menu'
tömmes ordentligt efter att ha raderat enSida
.Din
PageTest.php
ska se ut så här: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)); // Kontrollera om cachen har skrivits $ this-> assertNotNull (Cache :: get ('pages_for_menu')); allmän funktion test_clear_cache_after_save () // Ett testvärde sparas i cacheminnet :: put ('pages_for_menu', 'avalue', 5); // Det här ska städa värdet i cachen $ page = FactoryMuff :: skapa ('Sida'); $ This-> assertNull (Cache :: få (pages_for_menu ')); allmän funktion test_clear_cache_after_delete () $ page = FactoryMuff :: skapa ('sida'); // Ett testvärde sparas i cachemachin :: put ('pages_for_menu', 'value', 5); // Detta bör städa värdet i cachen $ page-> delete (); $ This-> assertNull (Cache :: få (pages_for_menu '));Användarmodell
När det gäller de tidigare presenterade modellerna har vi nu
Användare
. Här är koden för den modellen:'string', 'email' => 'email', 'password' => '123123', 'password_confirmation' => '123123'); / ** * Har många sidor * / offentliga funktionssidor () return $ this-> hasMany ('Sida', 'author_id'); / ** * Har många inlägg * / offentliga funktionstjänster () return $ this-> hasMany ('Post', 'author_id');Denna modell är frånvarande från tester.
Vi kan observera det, med undantag för relationer (vilket kan vara till hjälp för att testa) finns det ingen metodimplementering här. Vad sägs om autentisering? Tja, användningen av Confide-paketet ger redan genomförandet och testen för detta.
Testerna för
Zizaco \ Confide \ ConfideUser
finns i ConfideUserTest.php.Det är viktigt att bestämma klassansvar innan du skriver dina test. Testa alternativet till "återställ lösenordet" av a
Användare
skulle vara överflödig. Detta beror på att det korrekta ansvaret för detta test ligger inomZizaco \ Confide \ ConfideUser
; inte iAnvändare
.Detsamma gäller för data valideringstest. Eftersom paketet, Ardent, hanterar det här ansvaret, skulle det inte vara så bra att testa funktionaliteten igen.
Kortfattat: Håll dina tester rena och organiserade. Bestäm det korrekta ansvaret för varje klass och testa bara vad som strikt är sitt ansvar.
Slutsats
Användningen av en minnesdatabas är en bra metod att snabbt utföra tester mot en databas. Tack vare hjälp från vissa paket, som Ardent, FactoryMuff och Confide, kan du minimera antalet koden i dina modeller samtidigt som testen är rena och objektiva.
I uppföljningen till den här artikeln granskar vi Kontrollant testning. Håll dig igång!
Låt oss börja med Laravel 4, låt oss lära dig det väsentliga!