Testing som en chef i Laravel Modeller

Hvis du håper å lære hvorfor tester er fordelaktige, er dette ikke artikkelen for deg. I løpet av denne opplæringen vil jeg anta at du allerede forstår fordelene, og håper å lære hvordan du kan skrive og organisere tester i Laravel 4.

Versjon 4 av Laravel tilbyr alvorlige forbedringer i forhold til testing, sammenlignet med sin tidligere utgivelse. Dette er den første artikkelen i en serie som vil dekke hvordan man skriver tester for Laravel 4-applikasjoner. Vi starter serien ved å diskutere modelltesting.


Setup

In-memory database

Med mindre du kjører raske spørringer på databasen, lar Laravel din søknad forbli databasen agnostisk. Med en enkel driverendring kan applikasjonen din nå jobbe med andre DBMS (MySQL, PostgreSQL, SQLite, etc.). Blant standardalternativene tilbyr SQLite en merkelig, men svært nyttig funksjon: In-Memory databaser.

Med Sqlite kan vi sette databasetilkoblingen til :hukommelse:, som vil øke hastigheten på våre tester på grunn av at databasen ikke finnes på harddisken. Videre vil produksjons- / utviklingsdatabasen aldri bli fylt med gjenværende testdata, fordi forbindelsen, :hukommelse:, begynner alltid med en tom database.

Kort sagt: En in-memory database muliggjør rask og ren testing.

Innen app / konfig / testing katalog, opprett en ny fil, navngitt database.php, og fyll det med følgende innhold:

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

Det faktum at database.php er plassert i konfigurasjonen testing katalog betyr at disse innstillingene bare skal brukes når de er i et testmiljø (som Laravel automatisk angir). Som sådan, når søknaden din nås normalt, vil ikke i-minne databasen bli brukt.

Før du kjører tester

Siden databasen i minnet alltid er tom når en tilkobling er laget, er det viktig å migrere databasen før hver test. For å gjøre dette, åpne app / tester / TestCase.php og legg til følgende metode til slutten av klassen:

/ ** * Migrerer databasen og setter e-posten til "late". * Dette vil føre til at testene kjører raskt. * * / privat funksjon prepareForTests () Artisan :: call ('migrate'); Mail :: late (true); 

MERK: Den SETUP () Metoden utføres av PHPUnit før hver test.

Denne metoden vil forberede databasen, og endre statusen til Laravel Mailer klasse til late som. På den måten vil Mailer ikke sende noen ekte e-post når du kjører tester. I stedet vil det logge de "sendte" meldingene.

Å fullføre app / tester / TestCase.php, anrop prepareForTests () innenfor PHPUnit SETUP () metode, som vil utføres før hver test.

Ikke glem det ordnede :: SETUP (), som vi overskriver metoden til foreldreklassen.

/ ** * Standard forberedelse for hver test * * / offentlig funksjon setUp () foreldre :: setUp (); // Ikke glem dette! $ Dette-> prepareForTests (); 

På dette punktet, app / tester / TestCase.php bør se ut som følgende kode. Husk at createApplication er opprettet automatisk av Laravel. Du trenger ikke å bekymre deg for det.

// app / tester / TestCase.php prepareForTests ();  / ** * Oppretter applikasjonen. * * @return Symfony \ Component \ HttpKernel \ HttpKernelInterface * / offentlig funksjon createApplication () $ unitTesting = true; $ testEnvironment = 'testing'; retur krever __DIR __. '/ ... / ... / start.php';  / ** * Migrerer databasen og setter e-posten til "late". * Dette vil føre til at testene kjører raskt. * / privat funksjon prepareForTests () Artisan :: call ('migrate'); Mail :: late (true); 

Nå, for å skrive våre tester, bare utvide Testforsøk, og databasen vil bli initialisert og migrert før hver test.


Testene

Det er riktig å si at i denne artikkelen vil vi ikke følge TDD prosess. Problemet her er didaktisk, med målet å demonstrere hvordan testene kan skrives. På grunn av dette valgte jeg å avsløre de aktuelle modellene først, og deretter deres relaterte tester. Jeg tror at dette er en bedre måte å illustrere denne opplæringen på.

Konteksten til denne demo-applikasjonen er en enkel blogg / CMS, som inneholder brukere (autentisering), innlegg og statiske sider (som vises i menyen).

Postmodell

Vær oppmerksom på at modellen utvider klassen, Ardent, i stedet for Eloquent. Ardent er en pakke som gjør det enkelt å validere ved lagring av modellen (se $ regler eiendom).

Deretter har vi offentlig statisk $ fabrikk array, som utnytter FactoryMuff-pakken, for å hjelpe til med opprettelse av objekt ved testing.

Både Ardentx og FactoryMuff er tilgjengelig gjennom Packagist og Composer.

I vår Post modell, vi har et forhold til Bruker modell, gjennom magien forfatter metode.

Endelig har vi en enkel metode som returnerer datoen, formatert som "dag måned år".

// app / modeller / Post.php  'Required', // Post tittel 'slug' => 'Required | alpha_dash', // Post Url 'content' => 'Required', // Post innhold (Markdown) 'author_id' => 'required' numeric ' // Forfatter id); / ** * Array brukt av FactoryMuff for å lage Test objekter * / offentlig statisk $ factory = array ('title' => 'streng', 'slug' => 'streng', 'content' => 'tekst', 'author_id '=>' fabrikk | Bruker ', // Vil være ID for en eksisterende bruker.); / ** * Tilhører bruker * / offentlig funksjon forfatter () return $ this-> belongsTo ('User', 'author_id');  / ** * Få formatert innlegg dato * * @return streng * / offentlig funksjon postedAt () $ date_obj = $ this-> created_at; hvis (is_string ($ this-> created_at)) $ date_obj = DateTime :: createFromFormat ('Y-m-d H: i: s', $ date_obj); returnere $ date_obj-> format ('d / m / y'); 

Posttester

For å holde orden på det, har jeg lagt klassen med Post modell tester i app / prøver / modeller / PostTest.php. Vi går gjennom alle tester, en seksjon av gangen.

// app / tester / modeller / PostTest.php  

Vi utvider Testforsøk klasse, som er et krav til PHPUnit testing i Laravel. Også, ikke glem vårt prepareTests metode som vil løpe før hver test.

 offentlig funksjon test_relasjon_med_author () // Instantiate, fyll med verdier, lagre og returner $ post = FactoryMuff :: create ('Post'); // Takk til FactoryMuff, denne $ posten har en forfatter $ this-> assertEquals ($ post-> author_id, $ post-> author-> id); 

Denne testen er en "valgfri" en. Vi tester at forholdet "Post tilhører Bruker". Formålet her er for det meste å demonstrere funksjonaliteten til FactoryMuff.

Først når Post klassen har $ fabrikk statisk matrise som inneholder 'author_id' => 'fabrikk | bruker' (Merk kildekoden til modellen, vist ovenfor) Installer FactoryMuff en ny Bruker fyller sine attributter, lagre i databasen og til slutt returnere sin id til author_id attributt i Post.

For at dette skal være mulig, skal Bruker Modellen må ha en $ fabrikk array som også beskriver feltene sine.

Legg merke til hvordan du får tilgang til Bruker forhold gjennom $ Post-> forfatter. Som et eksempel kan vi få tilgang til $ Post-> autorise-> brukernavn, eller andre eksisterende brukerattributter.

FactoryMuff-pakken muliggjør rask etablering av konsistente objekter med det formål å teste, samtidig som man respekterer og instanser eventuelle nødvendige forhold. I dette tilfellet, når vi lager en Post med FactoryMuff :: lage ( 'Post') de Bruker vil også bli forberedt og gjort tilgjengelig.

 offentlig funksjon test_posted_at () // Instantiate, fyll med verdier, lagre og returner $ post = FactoryMuff :: create ('Post'); // Regular uttrykk som representerer d / m / Y mønster $ expected = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // True hvis preg_match finner mønsteret $ matches = (preg_match ($ expected, $ post-> postedAt ()))? sant: false; $ this-> assertTrue ($ matches); 

For å fullføre, avgjør vi om strengen returneres av postedAt () Metoden følger "dag / måned / år" format. For slik verifisering brukes et regulært uttrykk for å teste om mønsteret \ D 2 \ / \ d 2 \ / \ d 4 ("2 tall" + "bar" + "2 tall" + "bar" + "4 tall") er funnet.

Alternativt kan vi bruke PHPUnits assertRegExp matcher.

På dette punktet, den app / prøver / modeller / PostTest.php filen er som følger:

// app / tester / modeller / PostTest.php assertEquals ($ post-> author_id, $ post-> author-> id);  offentlig funksjon test_posted_at () // Instantiate, fyll med verdier, lagre og returner $ post = FactoryMuff :: create ('Post'); // Regular uttrykk som representerer d / m / Y mønster $ expected = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // True hvis preg_match finner mønsteret $ matches = (preg_match ($ expected, $ post-> postedAt ()))? sant: false; $ this-> assertTrue ($ matches); 

PS: Jeg valgte ikke å skrive navnet på testene i CamelCase for lesbarhetsformål. PSR-1 tilgi meg, men testRelationWithAuthor er ikke så lesbar som jeg personlig ville foretrekke. Du er fri til å bruke den stilen du mest foretrekker, selvfølgelig.

Sidemodell

Vårt CMS trenger en modell som representerer statiske sider. Denne modellen er implementert som følger:

 'Required', // Sidetittel 'slug' => 'Required | alpha_dash', // Slug (url) 'content' => 'nødvendig', // Innhold (markdown) 'author_id' => 'required' numeric ' , // Forfatter-ID); / ** * Array brukt av FactoryMuff * / offentlig statisk $ factory = array ('title' => 'streng', 'slug' => 'string', 'content' => 'text', 'author_id' => ' fabrikk | Bruker ', // Vil være ID for en eksisterende bruker.); / ** * Tilhører bruker * / offentlig funksjon forfatter () return $ this-> belongsTo ('User', 'author_id');  / ** * Gjør menyen med cachen * * @return-streng Html for sidelinker. * / offentlig statisk funksjon renderMenu () $ sider = Cache :: rememberForever ('pages_for_menu', funksjon () Return Page :: velg (array ('title', 'slug')) -> get () -> toArray ();); $ result = "; foreach ($ sider som $ side) $ result. = HTML :: action ('PagesController @ show', $ side ['title'], ['slug' => $ side ['slug'] ]). ' | '; returnere $ resultat; / ** * Glem cache når det er lagret * / offentlig funksjon afterSave ($ suksess) hvis ($ suksess) Cache :: glem (' pages_for_menu '); / ** * Glem cache når slettet * / offentlig funksjon slett () foreldre :: slett (); cache :: glem ('sider_for_meny');

Vi kan observere den statiske metoden, renderMenu (), Gir en rekke lenker for alle eksisterende sider. Denne verdien lagres i hurtigbuffertasten, 'Pages_for_menu'. På denne måten, i fremtiden, ringer til renderMenu (), det vil ikke være behov for å slå den virkelige databasen. Dette kan gi betydelige forbedringer i programmets ytelse.

Men hvis a Side er lagret eller slettet (afterSave () og delete () metoder), vil verdien av cachen bli slettet, forårsaker renderMenu () å gjenspeile den nye tilstanden til databasen. Så, hvis navnet på en side er endret, eller hvis den er slettet, vil nøkkel 'pages_for_menu' blir slettet fra hurtigbufferen. (Cache :: glemme ( 'pages_for_menu');)

MERK: Metoden, afterSave (), er tilgjengelig gjennom Ardent-pakken. Ellers ville det være nødvendig å implementere lagre() Metode for å rense hurtigbufferen og ringe forelder :: spare ();

Side tester

I: app / prøver / modeller / PageTest.php, vi skriver følgende tester:

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

Igjen har vi en "valgfri" test for å bekrefte forholdet. Som relasjoner er ansvaret for Belyse \ Database \ Veltalende, som allerede er dekket av Laravel sine egne tester, trenger vi ikke skrive en ny test for å bekrefte at denne koden fungerer som forventet.

 offentlig funksjon test_render_menu () $ sider = array (); for ($ 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));  // Sjekk om cachen er skrevet $ this-> assertNotNull (Cache :: get ('pages_for_menu')); 

Dette er en av de viktigste tester for Side modell. Først er det laget fire sider i til sløyfe. Etter det, resultatet av renderMenu () samtale er lagret i $ resultat variabel. Denne variabelen skal inneholde en HTML-streng, som inneholder lenker til eksisterende sider.

De for hver loop sjekker om sluggen (url) av hver side er til stede i $ resultat. Dette er nok, siden det eksakte formatet til HTML ikke er relevant for våre behov.

Til slutt bestemmer vi om cachenøkkelen, pages_for_menu, har noe lagret Med andre ord, gjorde renderMenu () samtalen lagret faktisk verdi til hurtigbufferen?

 offentlig funksjon test_clear_cache_after_save () // En testverdi lagres i cache Cache :: put ('pages_for_menu', 'avalue', 5); // Dette bør rense verdien i cachen $ page = FactoryMuff :: create ('Page'); $ Dette-> assertNull (Cache :: får ( 'pages_for_menu')); 

Denne testen tar sikte på å verifisere om, når du lagrer en ny Side, hurtigknappen 'Pages_for_menu' tømmes. De FactoryMuff :: lage ( 'side'); til slutt utløser lagre() metode, slik at det skulle være nok for nøkkelen, 'Pages_for_menu', å bli ryddet.

 offentlig funksjon test_clear_cache_after_delete () $ page = FactoryMuff :: create ('Page'); // En testverdi lagres i hurtigbufferen Cache: put ('pages_for_menu', 'value', 5); // Dette bør rengjøre verdien i cachen $ side-> slette (); $ Dette-> assertNull (Cache :: får ( 'pages_for_menu')); 

I likhet med den forrige testen, bestemmer denne om nøkkelen 'Pages_for_menu' tømmes riktig etter at du har slettet en Side.

Din PageTest.php skal se slik ut:

assertEquals ($ side-> author_id, $ page-> author-> id);  offentlig funksjon test_render_menu () $ sider = array (); for ($ 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));  // Sjekk om cachen er skrevet $ this-> assertNotNull (Cache :: get ('pages_for_menu'));  offentlig funksjon test_clear_cache_after_save () // En testverdi lagres i cache Cache :: put ('pages_for_menu', 'avalue', 5); // Dette bør rense verdien i cachen $ page = FactoryMuff :: create ('Page'); $ Dette-> assertNull (Cache :: får ( 'pages_for_menu'));  offentlig funksjon test_clear_cache_after_delete () $ page = FactoryMuff :: create ('Page'); // En testverdi lagres i hurtigbufferen Cache: put ('pages_for_menu', 'value', 5); // Dette bør rengjøre verdien i cachen $ side-> slette (); $ Dette-> assertNull (Cache :: får ( 'pages_for_menu')); 

Brukermodell

I tilknytning til tidligere presenterte modeller har vi nå Bruker. Her er koden for den modellen:

 'string', 'email' => 'email', 'password' => '123123', 'password_confirmation' => '123123',); / ** * Har mange sider * / offentlige funksjonssidene () return $ this-> hasMany ('Side', 'author_id');  / ** * Har mange innlegg * / offentlige funksjonstjenester () return $ this-> hasMany ('Post', 'author_id'); 

Denne modellen er fraværende fra tester.

Vi kan observere det, med unntak av relasjoner (som kan være nyttig å teste), er det ingen metodeimplementering her. Hva med autentisering? Vel, bruken av Confide-pakken gir allerede implementering og tester for dette.

Tester for Zizaco \ betro \ ConfideUser er lokalisert i ConfideUserTest.php.

Det er viktig å avgjøre klasseansvar før du skriver tester. Tester alternativet til "tilbakestill passordet" av a Bruker ville være overflødig. Dette skyldes at det riktige ansvaret for denne testen er innenfor Zizaco \ betro \ ConfideUser; ikke i Bruker.

Det samme gjelder for data valideringstester. Ettersom pakken, Ardent, håndterer dette ansvaret, ville det ikke gi stor mening å teste funksjonaliteten igjen.

Kort oppsummert: Hold tester rene og organisert. Bestem det riktige ansvaret for hver klasse, og test bare hva som er strengt ansvaret.


Konklusjon

Bruken av en in-memory database er en god praksis å utføre tester mot en database raskt. Takket være hjelp fra noen pakker, som Ardent, FactoryMuff og Confide, kan du minimere mengden kode i modellene dine, samtidig som testene blir rene og objektive.

I oppfølgeren til denne artikkelen vil vi se gjennom Controller testing. Følg med!

Fortsatt å komme i gang med Laravel 4, la oss lære deg det viktigste!