Testarea ca un șef în Laravel Modele

Dacă sperați să aflați de ce testele sunt benefice, acesta nu este articolul pentru dvs. De-a lungul acestui tutorial, voi presupune că deja înțelegeți avantajele și speră să aflați cum să vă scrieți și să organizați cele mai bune teste în Laravel 4.

Versiunea 4 a Laravel oferă îmbunătățiri serioase în ceea ce privește testarea, comparativ cu lansarea anterioară. Acesta este primul articol dintr-o serie care va acoperi modul de scriere a testelor pentru aplicațiile Laravel 4. Vom începe seria, discutând testul modelului.


Înființat

Baza de date în memorie

Cu excepția cazului în care executați interogări brute în baza de date, Laravel permite aplicației dvs. să rămână baza de date agnostică. Cu o simplă modificare a driverului, aplicația dvs. poate lucra acum cu alte DBMS-uri (MySQL, PostgreSQL, SQLite etc.). Printre opțiunile implicite, SQLite oferă o caracteristică ciudată, dar foarte utilă: baze de date în memorie.

Cu Sqlite, putem seta conexiunea bazei de date la :memorie:, ceea ce va accelera drastic testele noastre, datorită bazei de date care nu există pe hard disk. Mai mult decât atât, baza de date de producție / dezvoltare nu va fi populată niciodată cu date de test stânga, deoarece conexiunea, :memorie:, începe întotdeauna cu o bază de date goală.

Pe scurt: o bază de date în memorie permite teste rapide și curate.

În cadrul app / config / testare director, creați un nou fișier numit database.php, și completați-l cu următorul conținut:

// app / config / test / database.php  'sqlite', 'conexiuni' => array ('sqlite' => array ('driver' => 'sqlite', 'database' => ': memory';

Faptul că database.php este plasat în configurație testarea înseamnă că aceste setări vor fi utilizate numai atunci când se află într-un mediu de testare (pe care Laravel îl stabilește automat). Ca atare, când aplicația dvs. este accesată în mod normal, baza de date în memorie nu va fi utilizată.

Înainte de a începe testele

Deoarece baza de date în memorie este întotdeauna goală atunci când se face o conexiune, este important să migra baza de date înainte de fiecare test. Pentru a face acest lucru, deschideți-vă app / teste / TestCase.php și adăugați următoarea metodă la sfârșitul clasei:

/ ** * Migrează baza de date și setează mailer-ul pentru a "pretinde". * Acest lucru va face ca testele să ruleze rapid. * * / funcția privată prepareForTests () Artisan :: call ('migrate'); Mail :: prefaci (true); 

NOTĂ: The înființat() metoda este executată de PHPUnit înainte de fiecare test.

Această metodă va pregăti baza de date și va schimba starea lui Laravel Mailer clasa la pretinde. În acest fel, Mailer nu va trimite mesaje e-mail reale când se execută teste. În schimb, acesta va înregistra mesajele "trimise".

A finaliza app / teste / TestCase.php, apel prepareForTests () în cadrul PHPUnit înființat() care se va executa inainte de fiecare test.

Nu uitați părinte :: SetUp (), pe măsură ce suprascriem metoda din clasa părinte.

/ ** * Pregătirea implicită pentru fiecare test * * / funcția publică setUp () parent :: setUp (); // Nu uita asta! $ This-> prepareForTests (); 

In acest punct, app / teste / TestCase.php ar trebui să arate ca următorul cod. Sa nu uiti asta createApplication este creată automat de Laravel. Nu trebuie să vă faceți griji.

// app / teste / TestCase.php prepareForTests ();  / ** * Creează aplicația. * * @return Symfony \ Component \ HttpKernel \ HttpKernelInterface * / funcția publică createApplication () $ unitTesting = true; $ testEnvironment = 'testarea'; returul necesită __DIR __. "/ ... / ... /start.php";  / ** * Migrează baza de date și setează mailer-ul pentru a "pretinde". * Acest lucru va face ca testele să ruleze rapid. * / funcția privată prepareForTests () Artisan :: apel ('migrate'); Mail :: prefaci (true); 

Acum, pentru a scrie testele noastre, pur și simplu extindeți testcase, iar baza de date va fi inițializată și migrată înainte de fiecare încercare.


Testele

Este corect să spun că, în acest articol, nu vom urmări TDD proces. Problema aici este didactică, cu scopul de a demonstra cum pot fi scrise testele. Din acest motiv, am ales să dezvăluie mai întâi modelele în cauză și apoi testele aferente. Cred că aceasta este o modalitate mai bună de a ilustra acest tutorial.

Contextul acestei aplicații demo este un simplu blog / CMS, care conține utilizatori (autentificare), postări și pagini statice (care sunt afișate în meniu).

Modelul postului

Rețineți că modelul extinde clasa, Ardent, mai degrabă decât elocvent. Ardent este un pachet care permite validarea ușoară, după salvarea modelului (vezi norme $ proprietate).

Apoi, avem publică statică $ fabrică array, care utilizează pachetul FactoryMuff, pentru a ajuta la crearea obiectelor atunci când se testează.

Ambii Ardentx și FactoryMuff sunt disponibile prin intermediul ambalatorului și compozitorului.

În a noastră Post model, avem o relație cu Utilizator model, prin magie autor metodă.

În cele din urmă, avem o metodă simplă care returnează data, formatată ca "zi lună an".

// app / models / Post.php  'cerut', // Post tittle 'slug' => 'obligatoriu' alpha_dash ', // Post url' content '=>' obligatoriu ', // Post content (' Markdown ')' author_id '=> // ID autor); / ** * Array utilizat de FactoryMuff pentru a crea obiecte de testare * / public static $ factory = array ('title' => 'string', 'slug' => 'string', 'content' => 'text' '=>' fabrică 'Utilizator', // va fi id-ul unui utilizator existent); / ** * aparține utilizatorului * / autorului funcției publice () return $ this-> belongsTo ('Utilizator', 'author_id');  / ** * Obțineți data postare formatată * * @return string * / funcția publică postedAt () $ date_obj = $ this-> created_at; dacă (is_string ($ this-> created_at)) $ date_obj = DateTime :: createFromFormat ('Y-m-d H: i: s', $ date_obj); returnați $ date_obj-> format ('d / m / Y'); 

Teste postale

Pentru a păstra lucrurile organizate, am plasat clasa cu Post testele de model în app / teste / modele / PostTest.php. Vom face toate testele, o secțiune la un moment dat.

// app / teste / modele / PostTest.php  

Extinde testcase clasa, care este o cerință pentru testarea PHPUnit în Laravel. De asemenea, nu uitați de noi prepareTests care se va desfășura înainte de fiecare încercare.

 funcția publică test_relation_with_author () // Instanțiate, umpleți cu valori, salvați și returnați $ post = FactoryMuff :: create ('Post'); // Datorită lui FactoryMuff, această postare $ are un autor $ this-> assertEquals ($ post-> author_id, $ post-> author-> id); 

Acest test este unul "opțional". Testează faptul că relația "Post aparține lui Utilizator"Scopul aici este de a demonstra în mare măsură funcționalitatea FactoryMuff.

Odata ce Post clasa au fabrica $ array static conținând 'author_id' => 'fabrică' Utilizator ' (notați codul sursă al modelului, prezentat mai sus), FactoryMuff instantează un nou Utilizator umple atributele sale, salvează în baza de date și în cele din urmă returnează ID-ul la author_id atribut în Post.

Pentru ca acest lucru să fie posibil, Utilizator modelul trebuie să aibă a fabrica $ array care descrie și câmpurile sale.

Observați cum puteți accesa Utilizator relație prin $ Post-> autor. Ca exemplu, putem accesa $ Post-> author-> nume de utilizator, sau orice alt atribut utilizator existent.

Pachetul FactoryMuff permite instanționarea rapidă a obiectelor coerente în scopul testării, respectând și instanțiând orice relație necesară. În acest caz, când creăm a Post cu FactoryMuff :: crea ( 'Post') Utilizator vor fi, de asemenea, pregătite și puse la dispoziție.

 funcția publică test_posted_at () // Instanțiate, umpleți cu valori, salvați și returnați $ post = FactoryMuff :: create ('Post'); // Expresie regulată care reprezintă modelul d / m / Y $ expected = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // Adevărat dacă preg_match găsește modelul $ matches = (preg_match ($ expected, $ post-> postedAt ()))? adevarat fals; $ this-> assertTrue (meciuri $); 

Pentru a termina, vom determina dacă șirul returnat de către postedAt () Metoda urmează formatul "zi / lună / an". Pentru o astfel de verificare, o expresie regulată este utilizată pentru a testa dacă modelul \ D 2 \ / \ d 2 \ / \ d 4 ("2 numere" + "bar" + "2 numere" + "bar" + "4 numere") e gasit.

Alternativ, am putea folosi matricea assertRegExp a PHPUnit.

În acest moment, app / teste / modele / PostTest.php fișierul este după cum urmează:

// app / teste / modele / PostTest.php assertEquals ($ post-> autor_id, $ post-> autor-> id);  funcția publică test_posted_at () // Instanțiate, umpleți cu valori, salvați și returnați $ post = FactoryMuff :: create ('Post'); // Expresie regulată care reprezintă modelul d / m / Y $ expected = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // Adevărat dacă preg_match găsește modelul $ matches = (preg_match ($ expected, $ post-> postedAt ()))? adevarat fals; $ this-> assertTrue (meciuri $); 

PS: Am ales să nu scriu numele testelor în CamelCase pentru scopuri de citire. PSR-1 mă ierți, dar testRelationWithAuthor nu este la fel de ușor de citit cum aș prefera personal. Sunteți liber să folosiți stilul pe care îl preferați cel mai bine, desigur.

Model de pagină

CMS are nevoie de un model pentru a reprezenta pagini statice. Acest model este implementat după cum urmează:

 'obligatoriu', // Titlul paginii 'slug' => 'obligatoriu' alpha_dash ', // Continutul (markdown)' author_id '=> , // ID autor); / ** * Array folosit de FactoryMuff * / public static $ factory = array ('title' => 'string', 'slug' => fabrică | Utilizator ', // va fi id-ul unui utilizator existent.); / ** * aparține utilizatorului * / autorului funcției publice () return $ this-> belongsTo ('Utilizator', 'author_id');  / ** * Redă meniul folosind cache * * @return Html șir pentru link-uri de pagină. * / funcția statică publică renderMenu () $ pages = Cache :: rememberForever ('pages_for_menu', funcția () return Page :: select (array ('title', 'slug' ();); $ result = "; foreach (pagini $ ca pagina $) $ result. = HTML :: action ('PagesController @ show', $ page ['title'], ['slug' => $ page ['slug'] ]). " ($ succes) / ** * uitați cache atunci când ați salvat * / funcția publică dupăSave ($ success) if ($ success) Cache :: forget ('pages_for_menu'); eliminat * / funcția publică șterge () parent delete (); Cache :: forget ('pages_for_menu');

Putem observa că metoda statică, renderMenu (), redă un număr de linkuri pentru toate paginile existente. Această valoare este salvată în cheia cache, 'Pages_for_menu'. În acest fel, în viitoarele apeluri către renderMenu (), nu va fi nevoie să atingeți baza de date reală. Acest lucru poate oferi îmbunătățiri semnificative ale performanței aplicației noastre.

Cu toate acestea, dacă a Pagină este salvat sau șters (afterSave () și șterge() metode), valoarea cache-ului va fi eliminată, cauzând renderMenu () pentru a reflecta noua stare de date. Deci, dacă numele unei pagini este modificat sau dacă este șters, cheie 'pages_for_menu' este șters din memoria cache. (Cache :: uita ( 'pages_for_menu');)

NOTĂ: Metoda, afterSave (), este disponibil prin pachetul Ardent. În caz contrar, ar fi necesar să se pună în aplicare Salvați() metoda de curățare a cache-ului și a apelului părinte :: save ();

Teste de pagină

În: app / teste / modele / PageTest.php, vom scrie urmatoarele teste:

assertEquals ($ pagină-> autor_id, $ pagină-> autor-> id); 

Încă o dată, avem un test "opțional" pentru a confirma relația. Întrucât relațiile sunt responsabilitatea Illuminate \ Baza de date \ Elocvent, care este deja acoperit de testele lui Laravel, nu este nevoie să scriem un alt test pentru a confirma că acest cod funcționează așa cum era de așteptat.

 funcția publică test_render_menu () $ pages = array (); pentru ($ 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 ($ rezultat, $ pagină-> slug));  // Verificați dacă cache-ul a fost scris $ this-> assertNotNull (Cache :: get ('pages_for_menu')); 

Acesta este unul dintre cele mai importante teste pentru Pagină model. Mai întâi, patru pagini sunt create în pentru buclă. Ca urmare, rezultatul renderMenu () apelul este stocat în $ rezultat variabil. Această variabilă ar trebui să conțină un șir HTML care conține legături către paginile existente.

pentru fiecare buclă verifică dacă este prezentă urla (url) a fiecărei pagini $ rezultat. Acest lucru este suficient, deoarece formatul exact al codului HTML nu este relevant pentru nevoile noastre.

În cele din urmă, vom determina dacă cheia cache-ului, pages_for_menu, are ceva stocat. Cu alte cuvinte, a făcut-o renderMenu () apelul a salvat o anumită valoare în memoria cache?

 funcția publică test_clear_cache_after_save () // O valoare de test este salvată în memoria cache Cache :: put ('pages_for_menu', 'avalue', 5); // Aceasta ar trebui să curețe valoarea în cache $ page = FactoryMuff :: create ('Page'); $ This-> assertNull (Cache :: get ( 'pages_for_menu')); 

Acest test are scopul de a verifica dacă, atunci când salvați un nou Pagină, cheia cache-ului 'Pages_for_menu' este golit. FactoryMuff :: crea ( 'pagină'); în cele din urmă declanșează Salvați() astfel încât să fie suficientă pentru cheia, 'Pages_for_menu', pentru a fi eliminate.

 funcția publică test_clear_cache_after_delete () $ page = FactoryMuff :: create ('Page'); // Se salvează o valoare de test în memoria cache Cache :: put ('pages_for_menu', 'value', 5); // Aceasta ar trebui să curețe valoarea în cache $ page-> delete (); $ This-> assertNull (Cache :: get ( 'pages_for_menu')); 

Similar testului anterior, acesta determină dacă cheia 'Pages_for_menu' este golit în mod corespunzător după ce a fost șters Pagină.

Ta PageTest.php ar trebui să arate așa:

assertEquals ($ pagină-> autor_id, $ pagină-> autor-> id);  funcția publică test_render_menu () $ pages = array (); pentru ($ 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 ($ rezultat, $ pagină-> slug));  // Verificați dacă cache-ul a fost scris $ this-> assertNotNull (Cache :: get ('pages_for_menu'));  funcția publică test_clear_cache_after_save () // O valoare de test este salvată în memoria cache Cache :: put ('pages_for_menu', 'avalue', 5); // Aceasta ar trebui să curețe valoarea în cache $ page = FactoryMuff :: create ('Page'); $ This-> assertNull (Cache :: get ( 'pages_for_menu'));  funcția publică test_clear_cache_after_delete () $ page = FactoryMuff :: create ('Page'); // Se salvează o valoare de test în memoria cache Cache :: put ('pages_for_menu', 'value', 5); // Aceasta ar trebui să curețe valoarea în cache $ page-> delete (); $ This-> assertNull (Cache :: get ( 'pages_for_menu')); 

Modelul utilizatorului

Legat de modelele prezentate anterior, avem acum Utilizator. Iată codul pentru modelul respectiv:

 'șir', 'email' => 'email', 'parola' => '123123', 'password_confirmation' => '123123',); / ** * Are multe pagini * / pagini de funcții publice () return $ this-> hasMany ('Page', 'author_id');  / ** * Are numeroase postări * / posturi de funcții publice () return $ this-> hasMany ('Post', 'author_id'); 

Acest model lipsește testelor.

Putem observa că, cu excepția relațiilor (care pot fi utile pentru testare), nu există nici o implementare a metodei aici. Cum rămâne cu autentificarea? Utilizarea pachetului Confide oferă deja implementarea și testele pentru acest lucru.

Testele pentru Zizaco \ Încredeți \ ConfideUser sunt situate în ConfideUserTest.php.

Este important să determinați responsabilitățile de clasă înainte de a vă scrie testele. Testarea opțiunii pentru "resetați parola" a Utilizator ar fi redundant. Acest lucru se datorează faptului că responsabilitatea corespunzătoare pentru acest test este înăuntru Zizaco \ Încredeți \ ConfideUser; nu în Utilizator.

Același lucru este valabil și pentru testele de validare a datelor. Deoarece pachetul, Ardent, se ocupă de această responsabilitate, nu ar avea sens să testeze din nou funcționalitatea.

Pe scurt: testați-vă curat și organizat. Determinați responsabilitatea corespunzătoare a fiecărei clase și testați numai ceea ce este strict responsabilitatea acesteia.


Concluzie

Utilizarea unei baze de date în memorie este o bună practică pentru a executa rapid testele împotriva unei baze de date. Datorită ajutorului oferit de unele pachete, cum ar fi Ardent, FactoryMuff și Confide, puteți minimiza cantitatea de cod din modelele dvs., păstrând în același timp testele curate și obiective.

În continuarea acestui articol, vom examina Controlor testare. Rămâneți aproape!

Încă începeți cu Laravel 4, să vă învățăm lucrurile esențiale!

Cod