Als u hoopt te leren waarom tests gunstig zijn, is dit niet het artikel voor u. In de loop van deze tutorial ga ik ervan uit dat je de voordelen al begrijpt en hoop je te leren hoe je het best je tests kunt schrijven en organiseren in Laravel 4.
Versie 4 van Laravel biedt serieuze verbeteringen ten opzichte van testen in vergelijking met de vorige versie. Dit is het eerste artikel van een serie dat gaat over het schrijven van tests voor Laravel 4-toepassingen. We beginnen de serie door modeltesten te bespreken.
Tenzij u raw-query's uitvoert in uw database, staat Laravel uw applicatie toe om database-agnostisch te blijven. Met een eenvoudige wijziging van stuurprogramma kan uw toepassing nu met andere DBMS werken (MySQL, PostgreSQL, SQLite, enz.). Van de standaardopties biedt SQLite een eigenaardige, maar zeer nuttige functie: databases in het geheugen.
Met Sqlite kunnen we de databaseverbinding instellen :geheugen:
, die onze testen drastisch zal versnellen, vanwege de database die niet op de harde schijf aanwezig is. Bovendien zal de productie / ontwikkelingsdatabase nooit worden gevuld met overgebleven testgegevens, omdat de verbinding, :geheugen:
, begint altijd met een lege database.
Kort gezegd: een database in het geheugen zorgt voor snelle en schone tests.
Binnen de app / config / testen
map, maak een nieuw bestand met de naam database.php
, en vul het met de volgende inhoud:
// app / config / testing / database.php 'sqlite', 'connections' => array ('sqlite' => array ('driver' => 'sqlite', 'database' => ': geheugen:', 'prefix' => "),));
Het feit dat database.php
wordt in de configuratie geplaatst testing
map betekent dat deze instellingen alleen worden gebruikt in een testomgeving (die Laravel automatisch instelt). Als zodanig, wanneer uw toepassing normaal wordt geopend, zal de in-memory database niet worden gebruikt.
Omdat de in-memory database altijd leeg is wanneer een verbinding tot stand is gebracht, is het belangrijk om trekken de database vóór elke test. Om dit te doen, open app / testen / TestCase.php
en voeg de volgende methode toe aan het einde van de klas:
/ ** * Migreert de database en stelt de mailer in op 'doen alsof'. * Hierdoor zullen de tests snel worden uitgevoerd. * * / private function prepareForTests () Artisan :: call ('migrate'); Mail :: pretenderen (true);
Merk op
opstelling()
methode wordt vóór elke test door PHPUnit uitgevoerd.
Deze methode bereidt de database voor en verandert de status van Laravel's verzender
les naar doen alsof
. Op deze manier zal de Mailer geen echte e-mail verzenden tijdens het uitvoeren van tests. In plaats daarvan zal het de "verzonden" berichten loggen.
Beëindigen app / testen / TestCase.php
, telefoontje prepareForTests ()
binnen de PHPUnit opstelling()
methode, die vóór elke test zal worden uitgevoerd.
Vergeet het
ouder :: Instellingen ()
, omdat we de methode van de ouderklasse overschrijven.
/ ** * Standaardvoorbereiding voor elke test * * / openbare functiesetUp () parent :: setUp (); // Vergeet dit niet! $ This-> prepareForTests ();
Op dit punt, app / testen / TestCase.php
zou moeten lijken op de volgende code. Onthoudt dat createApplication
wordt automatisch gemaakt door Laravel. U hoeft zich er geen zorgen over te maken.
// app / tests / TestCase.php prepareForTests (); / ** * Creëert de applicatie. * * @return Symfony \ Component \ HttpKernel \ HttpKernelInterface * / public function createApplication () $ unitTesting = true; $ testEnvironment = 'testen'; return vereist __DIR __. '/ ... / ... /start.php'; / ** * Migreert de database en stelt de mailer in op 'doen alsof'. * Hierdoor zullen de tests snel worden uitgevoerd. * / private function prepareForTests () Artisan :: call ('migrate'); Mail :: pretenderen (true);
Nu, om onze tests te schrijven, eenvoudig uitbreiden testcase
, en de database wordt vóór elke test geïnitialiseerd en gemigreerd.
Het is correct om te zeggen dat we in dit artikel de. Niet zullen volgen TDD werkwijze. Het probleem is hier didactisch, met als doel om aan te tonen hoe de tests kunnen worden geschreven. Om deze reden heb ik ervoor gekozen eerst de betreffende modellen te onthullen, en daarna hun bijbehorende tests. Ik geloof dat dit een betere manier is om deze tutorial te illustreren.
De context van deze demotoepassing is een eenvoudige blog / CMS met gebruikers (authenticatie), berichten en statische pagina's (die in het menu worden weergegeven).
Let op: het model breidt de klas uit, Ardent, in plaats van Welsprekend. Ardent is een pakket dat zorgt voor eenvoudige validatie, na het opslaan van het model (zie de $ regels
eigendom).
Vervolgens hebben we de openbare statische $ fabriek
array, die gebruikmaakt van het FactoryMuff-pakket, om te helpen bij het maken van objecten tijdens het testen.
Beide Ardentx en FactoryMuff zijn beschikbaar via Packagist en Composer.
In onze Post
model, we hebben een relatie met de Gebruiker
model, door de magie schrijver
methode.
Ten slotte hebben we een eenvoudige methode die de datum retourneert, opgemaakt als "dag maand jaar".
// app / models / Post.php 'required', // Post tittle 'slug' => 'required | alpha_dash', // Post URL 'content' => 'required', // Inhoud plaatsen (Markdown) 'author_id' => 'verplicht | numeriek', // Auteur id); / ** * Array gebruikt door FactoryMuff om testobjecten te maken * / public static $ factory = array ('title' => 'string', 'slug' => 'string', 'content' => 'text', 'author_id '=>' factory | User ', // is het ID van een bestaande gebruiker.); / ** * Behoort tot gebruiker * / public function author () return $ this-> belongTo ('User', 'author_id'); / ** * Ontvang opgemaakte postdatum * * @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); return $ date_obj-> format ('d / m / Y');
Om dingen georganiseerd te houden, heb ik de klas geplaatst met de Post
modelproeven in app / testen / modellen / PostTest.php
. We zullen alle tests doorlopen, een sectie per keer.
// app / tests / models / PostTest.phpWe verlengen de
testcase
klasse, wat een vereiste is voor PHPUnit-testen in Laravel. Vergeet ook ons nietprepareTests
methode die voor elke test wordt uitgevoerd.public function test_relation_with_author () // Instantiëren, invullen met waarden, opslaan en terugzenden $ post = FactoryMuff :: create ('Post'); // Dankzij FactoryMuff heeft deze $ post een auteur $ this-> assertEquals ($ post-> author_id, $ post-> author-> id);Deze test is een "optionele" test. We testen dat de relatie "
Post
hoort bijGebruiker
Het doel is hier vooral om de functionaliteit van FactoryMuff te demonstreren.Zodra de
Post
klasse hebben de$ fabriek
statische array met'author_id' => 'factory | Gebruiker'
(let op de broncode van het model, hierboven weergegeven) de FactoryMuff maakt een nieuwGebruiker
vult zijn attributen, slaat op in de database en retourneert uiteindelijk zijn id naar deauthor_id
attribuut in dePost
.Om dit mogelijk te maken, is de
Gebruiker
model moet een hebben$ fabriek
array die ook de velden beschrijft.Merk op hoe je toegang hebt tot de
Gebruiker
relatie door$ Post-> auteur
. Als voorbeeld hebben we toegang tot de$ Post-> Auteur-> gebruikersnaam
, of een ander bestaand gebruikerskenmerk.Het FactoryMuff-pakket maakt een snelle instantiatie mogelijk van consistente objecten met het oog op testen, terwijl de benodigde relaties worden gerespecteerd en geïnstantieerd. In dit geval, wanneer we een maken
Post
metFactoryMuff :: te maken ( 'Post')
deGebruiker
zal ook worden voorbereid en beschikbaar gemaakt.public function test_posted_at () // Instantiëren, invullen met waarden, opslaan en terugzenden $ post = FactoryMuff :: create ('Post'); // Reguliere expressie die d / m / Y-patroon vertegenwoordigt $ expected = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // Waar als preg_match het patroon vindt $ matches = (preg_match ($ expected, $ post-> postedAt ()))? waar onwaar; $ this-> assertTrue ($ matches);Om te eindigen, bepalen we of de string geretourneerd door de
postedAt ()
methode volgt het formaat "dag / maand / jaar". Voor een dergelijke verificatie wordt een reguliere expressie gebruikt om te testen of het patroon\ D 2 \ / \ d 2 \ / \ d 4
("2 cijfers" + "balk" + "2 cijfers" + "balk" + "4 cijfers") is gevonden.Als alternatief kunnen we de assertRegExp-matcher van PHPUnit gebruiken.
Op dit punt, de
app / testen / modellen / PostTest.php
bestand is als volgt:// app / tests / models / PostTest.php assertEquals ($ post-> author_id, $ post-> author-> id); openbare functie test_posted_at () // Onmiddellijk, vul met waarden, sla op en retourneer $ post = FactoryMuff :: create ('Post'); // Reguliere expressie die d / m / Y-patroon vertegenwoordigt $ expected = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // Waar als preg_match het patroon vindt $ matches = (preg_match ($ expected, $ post-> postedAt ()))? waar onwaar; $ this-> assertTrue ($ matches);PS: ik heb ervoor gekozen de naam van de tests in CamelCase niet te schrijven omwille van de leesbaarheid. PSR-1 vergeef me, maar
testRelationWithAuthor
is niet zo leesbaar als ik persoonlijk zou willen. Je bent natuurlijk vrij om de stijl te gebruiken die je het liefst hebt.Paginamodel
Ons CMS heeft een model nodig om statische pagina's weer te geven. Dit model is als volgt geïmplementeerd:
'vereist', // Paginatitel 'slug' => 'vereist | alpha_dash', // Slug (url) 'content' => 'vereist', // Inhoud (markdown) 'author_id' => 'verplicht | numeriek' , // Auteur id); / ** * Array gebruikt door FactoryMuff * / public static $ factory = array ('title' => 'string', 'slug' => 'string', 'content' => 'text', 'author_id' => ' fabriek | Gebruiker ', // is het ID van een bestaande gebruiker.); / ** * Behoort tot gebruiker * / public function author () return $ this-> belongTo ('User', 'author_id'); / ** * Herstelt het menu met cache * * @return string Html voor paginalinks. * / public static function renderMenu () $ pages = Cache :: rememberForever ('pages_for_menu', function () return Page :: select (array ('title', 'slug')) -> get () -> toArray ();); $ result = "; foreach ($ pagina's als $ pagina) $ result. = HTML :: actie ('PagesController @ show', $ page ['title'], ['slug' => $ page ['slug'] ]). ' | '; return $ result; / ** * Cache vergeten wanneer opgeslagen * / public function afterSave ($ succes) if ($ succes) Cache :: forget (' pages_for_menu '); / ** * Cache vergeten wanneer delete * / public function delete () parent :: delete (); Cache :: forget ('pages_for_menu');We kunnen waarnemen dat de statische methode,
renderMenu ()
, rendert een aantal links voor alle bestaande pagina's. Deze waarde wordt opgeslagen in de cachesleutel,'Pages_for_menu'
. Op deze manier, in toekomstige oproepen naarrenderMenu ()
, het is niet nodig om de echte database te raken. Dit kan de prestaties van onze applicatie aanzienlijk verbeteren.Echter, als een
Pagina
wordt opgeslagen of verwijderd (afterSave ()
enwissen ()
methoden), wordt de waarde van de cache gewist, waardoor derenderMenu ()
om de nieuwe staat van de database weer te geven. Dus als de naam van een pagina is gewijzigd of als deze is verwijderd, is desleutel 'pages_for_menu'
wordt uit de cache gewist. (Cache :: vergeten ( 'pages_for_menu');
)OPMERKING: de methode,
afterSave ()
, is beschikbaar via het Ardent-pakket. Anders zou het moeten worden geïmplementeerdopslaan()
methode om de cache te reinigen en te bellenouder :: save ()
;Paginatests
In:
app / testen / modellen / PageTest.php
, we zullen de volgende tests schrijven:assertEquals ($ page-> author_id, $ page-> author-> id);Nogmaals, we hebben een "optionele" test om de relatie te bevestigen. Zoals relaties de verantwoordelijkheid zijn van
Verlichten \ Database \ Welsprekend
, die al door de eigen tests van Laravel wordt gedekt, hoeven we geen nieuwe test te schrijven om te bevestigen dat deze code werkt zoals verwacht.openbare functie test_render_menu () $ pages = array (); voor ($ 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)); // Controleer of cache is geschreven $ this-> assertNotNull (Cache :: get ('pages_for_menu'));Dit is een van de belangrijkste tests voor de
Pagina
model. Eerst worden vier pagina's gemaakt in devoor
lus. Hierna is het resultaat van derenderMenu ()
oproep wordt opgeslagen in de$ result
variabel. Deze variabele moet een HTML-tekenreeks bevatten met koppelingen naar de bestaande pagina's.De
foreach
loop controleert of de slug (url) van elke pagina aanwezig is$ result
. Dit is voldoende, omdat het exacte formaat van de HTML niet relevant is voor onze behoeften.Ten slotte bepalen we of de cachesleutel,
pages_for_menu
, heeft iets opgeslagen. Met andere woorden, deed derenderMenu ()
call heeft daadwerkelijk wat waarde in de cache opgeslagen?public function test_clear_cache_after_save () // Een testwaarde wordt opgeslagen in cache Cache :: put ('pages_for_menu', 'avalue', 5); // Dit zou de waarde in cache moeten opschonen $ page = FactoryMuff :: create ('Page'); $ This-> assertNull (Cache :: te krijgen ( 'pages_for_menu'));Deze test is bedoeld om te verifiëren of, bij het opslaan van een nieuwe
Pagina
, de cachesleutel'Pages_for_menu'
is geleegd. DeFactoryMuff :: te maken ( 'Pagina');
uiteindelijk triggert deopslaan()
methode, dus dat zou voor de sleutel moeten volstaan,'Pages_for_menu'
, worden gewist.public function test_clear_cache_after_delete () $ page = FactoryMuff :: create ('Page'); // Een testwaarde wordt opgeslagen in de cache Cache :: put ('pages_for_menu', 'value', 5); // Dit zou de waarde in cache $ page-> delete () moeten opschonen; $ This-> assertNull (Cache :: te krijgen ( 'pages_for_menu'));Vergelijkbaar met de vorige test, deze bepaalt of de sleutel
'Pages_for_menu'
is leeggemaakt na het verwijderen van aPagina
.Jouw
PageTest.php
zou er zo uit moeten zien:assertEquals ($ page-> author_id, $ page-> author-> id); public function test_render_menu () $ pages = array (); voor ($ 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)); // Controleer of cache is geschreven $ this-> assertNotNull (Cache :: get ('pages_for_menu')); public function test_clear_cache_after_save () // Een testwaarde wordt opgeslagen in cache Cache :: put ('pages_for_menu', 'avalue', 5); // Dit zou de waarde in cache moeten opschonen $ page = FactoryMuff :: create ('Page'); $ This-> assertNull (Cache :: te krijgen ( 'pages_for_menu')); public function test_clear_cache_after_delete () $ page = FactoryMuff :: create ('Page'); // Een testwaarde wordt opgeslagen in de cache Cache :: put ('pages_for_menu', 'value', 5); // Dit zou de waarde in cache $ page-> delete () moeten opschonen; $ This-> assertNull (Cache :: te krijgen ( 'pages_for_menu'));Gebruikersmodel
Gerelateerd aan de eerder gepresenteerde modellen, hebben we nu de
Gebruiker
. Hier is de code voor dat model:'string', 'email' => 'email', 'wachtwoord' => '123123', 'password_confirmation' => '123123',); / ** * Heeft veel pagina's * / public function pages () return $ this-> hasMany ('Page', 'author_id'); / ** * Heeft veel posts * / public function posts () return $ this-> hasMany ('Post', 'author_id');Dit model is afwezig van tests.
We kunnen vaststellen dat, met uitzondering van relaties (die nuttig kunnen zijn om te testen), hier geen methode wordt geïmplementeerd. Hoe zit het met authenticatie? Welnu, het gebruik van het Confide-pakket biedt al de implementatie en tests hiervoor.
De tests voor
Zizaco \ Confide \ ConfideUser
bevinden zich in ConfideUserTest.php.Het is belangrijk om de verantwoordelijkheden van de klas te bepalen voordat u uw tests schrijft. De optie testen om "reset het wachtwoord" van a
Gebruiker
zou overbodig zijn. Dit komt omdat de juiste verantwoordelijkheid voor deze test aanwezig isZizaco \ Confide \ ConfideUser
; niet inGebruiker
.Hetzelfde geldt voor gegevensvalidatietests. Omdat het pakket, Ardent, deze verantwoordelijkheid op zich neemt, zou het niet veel zin hebben om de functionaliteit opnieuw te testen.
Kortom: houd uw tests schoon en georganiseerd. Bepaal de eigen verantwoordelijkheid van elke klas en test alleen wat zijn verantwoordelijkheid is.
Conclusie
Het gebruik van een in-memory database is een goede gewoonte om snel tests tegen een database uit te voeren. Dankzij de hulp van sommige pakketten, zoals Ardent, FactoryMuff en Confide, kunt u de hoeveelheid code in uw modellen minimaliseren, terwijl de tests schoon en objectief blijven.
In het vervolg op dit artikel zullen we beoordelen controleur testen. Blijf kijken!
Ga aan de slag met Laravel 4 en laat ons je de essentie leren!