Se speri di imparare perché i test sono vantaggiosi, questo non è l'articolo per te. Nel corso di questo tutorial, presumo che tu abbia già compreso i vantaggi e speri di imparare come scrivere e organizzare al meglio i tuoi test in Laravel 4.
La versione 4 di Laravel offre notevoli miglioramenti in relazione ai test, rispetto alla versione precedente. Questo è il primo articolo di una serie che tratterà come scrivere test per le applicazioni Laravel 4. Inizieremo la serie discutendo i test dei modelli.
A meno che non si eseguano query non elaborate sul proprio database, Laravel consente alla propria applicazione di rimanere indipendente dal database. Con una semplice modifica del driver, l'applicazione ora può funzionare con altri DBMS (MySQL, PostgreSQL, SQLite, ecc.). Tra le opzioni predefinite, SQLite offre una caratteristica particolare, ma molto utile: i database in memoria.
Con Sqlite, possiamo impostare la connessione del database a :memoria:
, che velocizzerà drasticamente i nostri test, a causa del database non esistente sul disco rigido. Inoltre, il database di produzione / sviluppo non verrà mai popolato con dati test rimanenti, poiché la connessione, :memoria:
, inizia sempre con un database vuoto.
In breve: un database in memoria consente test rapidi e puliti.
All'interno del app / config / testing
directory, creare un nuovo file, denominato database.php
, e riempilo con il seguente contenuto:
// app / config / testing / database.php 'sqlite', 'connections' => array ('sqlite' => array ('driver' => 'sqlite', 'database' => ': memoria:', 'prefix' => "),));
Il fatto che database.php
è posizionato all'interno della configurazione analisi
directory significa che queste impostazioni saranno utilizzate solo in un ambiente di test (che Laravel imposta automaticamente). Pertanto, quando si accede normalmente alla propria applicazione, il database in memoria non verrà utilizzato.
Poiché il database in memoria è sempre vuoto quando viene stabilita una connessione, è importante farlo migrare il database prima di ogni test. Per fare questo, apri app / test / TestCase.php
e aggiungi il seguente metodo alla fine della classe:
/ ** * Migra il database e imposta il mailer su 'fingere'. * In questo modo i test verranno eseguiti rapidamente. * * / funzione privata prepareForTests () Artisan :: call ('migrate'); Mail :: finta (true);
Notare la
impostare()
il metodo viene eseguito da PHPUnit prima di ogni test.
Questo metodo preparerà il database e cambierà lo stato di Laravel Mailer
classe a fingere
. In questo modo, il Mailer non invierà alcuna vera e-mail durante l'esecuzione dei test. Invece, registrerà i messaggi "inviati".
Per finalizzare app / test / TestCase.php
, chiamata prepareForTests ()
all'interno di PHPUnit impostare()
metodo, che verrà eseguito prima di ogni test.
Non dimenticare il
parent :: setup ()
, poiché stiamo sovrascrivendo il metodo della classe genitore.
/ ** * Preparazione predefinita per ogni test * * / funzione pubblica setUp () parent :: setUp (); // Non dimenticarlo! $ This-> prepareForTests ();
A questo punto, app / test / TestCase.php
dovrebbe apparire come il seguente codice. Ricordatelo createApplication
è creato automaticamente da Laravel. Non devi preoccuparti di questo.
// app / tests / TestCase.php prepareForTests (); / ** * Crea l'applicazione. * * @return Symfony \ Component \ HttpKernel \ HttpKernelInterface * / public function createApplication () $ unitTesting = true; $ testEnvironment = 'testing'; return richiede __DIR __. '/ ... / ... /start.php'; / ** * Migra il database e imposta il mailer su 'fingere'. * In questo modo i test verranno eseguiti rapidamente. * / private function prepareForTests () Artisan :: call ('migrate'); Mail :: finta (true);
Ora, per scrivere i nostri test, semplicemente estendilo TestCase
, e il database verrà inizializzato e migrato prima di ogni test.
È corretto dire che, in questo articolo, non seguiremo il TDD processi. Il problema qui è didattico, con l'obiettivo di dimostrare come i test possono essere scritti. Per questo motivo, ho scelto di rivelare prima i modelli in questione e poi i relativi test. Credo che questo sia un modo migliore per illustrare questo tutorial.
Il contesto di questa applicazione demo è un semplice blog / CMS, contenente utenti (autenticazione), post e pagine statiche (che sono mostrate nel menu).
Si prega di notare che il modello estende la classe, Ardent, piuttosto che Eloquent. Ardent è un pacchetto che facilita la convalida, salvando il modello (vedi il $ regole
proprietà).
Successivamente, abbiamo il fabbrica statica $ pubblica
array, che sfrutta il pacchetto FactoryMuff per facilitare la creazione dell'oggetto durante il test.
Tutti e due Ardentx e FactoryMuff sono disponibili tramite Packagist e Composer.
Nel nostro Inviare
modello, abbiamo una relazione con il Utente
modello, attraverso la magia autore
metodo.
Infine, abbiamo un metodo semplice che restituisce la data, formattata come "giorno mese Anno".
// app / models / Post.php 'required', // Post tittle 'slug' => 'required | alpha_dash', // Post Url 'content' => 'required', // Posta il contenuto (Markdown) 'author_id' => 'required | numerico', // id dell'autore); / ** * Matrice utilizzata da FactoryMuff per creare oggetti Test * / public static $ factory = array ('title' => 'string', 'slug' => 'stringa', 'contenuto' => 'testo', 'id_autore '=>' factory | User ', // sarà l'id di un utente esistente.); / ** * Appartiene all'utente * / public function author () return $ this-> belongsTo ('User', 'author_id'); / ** * Ottieni la data del post formattato * * @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');
Per mantenere le cose organizzate, ho messo la classe con il Inviare
test modello in app / test / modelli / PostTest.php
. Analizzeremo tutti i test, una sezione alla volta.
// app / tests / models / PostTest.phpEstendiamo il
TestCase
classe, che è un requisito per il testing PHPUnit in Laravel. Inoltre, non dimenticare il nostroprepareTests
metodo che verrà eseguito prima di ogni test.funzione pubblica test_relation_with_author () // Istanziare, riempire con valori, salvare e restituire $ post = FactoryMuff :: create ('Post'); // Grazie a FactoryMuff, questo $ post ha un autore $ this-> assertEquals ($ post-> author_id, $ post-> autore-> id);Questo test è uno "opzionale". Stiamo testando che la relazione "
Inviare
appartiene aUtente
"Lo scopo qui è principalmente quello di dimostrare la funzionalità di FactoryMuff.Una volta il
Inviare
la classe ha il$ fabbrica
array statico contenente'author_id' => 'factory | User'
(notare il codice sorgente del modello, mostrato sopra) il FactoryMuff istanzia un nuovoUtente
riempie i suoi attributi, salva nel database e infine restituisce il suo id alauthor_id
attributo nelInviare
.Perché ciò sia possibile, il
Utente
il modello deve avere un$ fabbrica
array che descrive anche i suoi campi.Si noti come è possibile accedere a
Utente
relazione attraverso$ Post-> autore
. Ad esempio, possiamo accedere a$ Post-> auto-> nome utente
, o qualsiasi altro attributo utente esistente.Il pacchetto FactoryMuff consente una rapida istanziazione di oggetti coerenti ai fini del test, rispettando e istanziando le relazioni necessarie. In questo caso, quando creiamo a
Inviare
conFactoryMuff :: Create ( 'Post')
ilUtente
sarà anche preparato e reso disponibile.funzione pubblica test_posted_at () // Istanziare, riempire con valori, salvare e restituire $ post = FactoryMuff :: create ('Post'); // Espressione regolare che rappresenta il pattern d / m / Y $ expected = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // Vero se preg_match trova lo schema $ matches = (preg_match ($ expected, $ post-> postedAt ()))? vero falso; $ this-> assertTrue ($ matches);Per finire, determiniamo se la stringa restituita da
postedAt ()
il metodo segue il formato "giorno / mese / anno". Per tale verifica, viene utilizzata un'espressione regolare per verificare se il modello\ D 2 \ / \ d 2 \ / \ d 4
("2 numeri" + "bar" + "2 numeri" + "bar" + "4 numeri") è stato trovato.In alternativa, potremmo usare il matcher assertRegExp di PHPUnit.
A questo punto, il
app / test / modelli / PostTest.php
il file è il seguente:// app / tests / models / PostTest.php assertEquals ($ post-> author_id, $ post-> autore-> id); public function test_posted_at () // Istanziare, riempire con valori, salvare e restituire $ post = FactoryMuff :: create ('Post'); // Espressione regolare che rappresenta il pattern d / m / Y $ expected = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // Vero se preg_match trova lo schema $ matches = (preg_match ($ expected, $ post-> postedAt ()))? vero falso; $ this-> assertTrue ($ matches);PS: ho scelto di non scrivere il nome dei test in CamelCase per motivi di leggibilità. PSR-1 perdonami, ma
testRelationWithAuthor
non è leggibile come preferirei personalmente. Sei libero di usare lo stile che preferisci, ovviamente.Modello di pagina
Il nostro CMS necessita di un modello per rappresentare pagine statiche. Questo modello è implementato come segue:
'required', // Titolo Pagina 'slug' => 'richiesto | alpha_dash', // Slug (url) 'content' => 'required', // Content (markdown) 'author_id' => 'required | numerico' , // id dell'autore); / ** * Matrice utilizzata da FactoryMuff * / public static $ factory = array ('title' => 'string', 'slug' => 'string', 'content' => 'text', 'author_id' => ' factory | User ', // sarà l'id di un utente esistente.); / ** * Appartiene all'utente * / public function author () return $ this-> belongsTo ('User', 'author_id'); / ** * Esegue il rendering del menu utilizzando la cache * * @return string Html per i collegamenti di pagina. * / 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; / ** * Dimentica la cache quando è salvato * / public function afterSave ($ success) if ($ success) Cache :: forget (' pages_for_menu '); / ** * Dimentica la cache quando cancellato * / public function delete () parent :: delete (); Cache :: forget ('pages_for_menu');Possiamo osservare che il metodo statico,
renderMenu ()
, esegue il rendering di un numero di collegamenti per tutte le pagine esistenti. Questo valore è salvato nella chiave di cache,'Pages_for_menu'
. In questo modo, in future chiamate arenderMenu ()
, non ci sarà bisogno di colpire il vero database. Questo può fornire miglioramenti significativi alle prestazioni della nostra applicazione.Tuttavia, se a
Pagina
viene salvato o cancellato (AfterSave ()
eElimina()
metodi), il valore della cache verrà cancellato, causando ilrenderMenu ()
per riflettere il nuovo stato del database. Quindi, se il nome di una pagina viene modificato, o se viene eliminato, ilchiave 'pages_for_menu'
è cancellato dalla cache. (Cache :: dimenticare ( 'pages_for_menu');
)NOTA: il metodo,
AfterSave ()
, è disponibile tramite il pacchetto Ardent. Altrimenti, sarebbe necessario implementare ilsalvare()
metodo per pulire la cache e chiamareparent :: save ()
;Test della pagina
Nel:
app / test / modelli / PageTest.php
, scriveremo i seguenti test:assertEquals ($ page-> author_id, $ page-> author-> id);Ancora una volta, abbiamo un test "opzionale" per confermare la relazione. Poiché le relazioni sono responsabilità di
Illuminare \ Database \ Eloquente
, che è già coperto dagli stessi test di Laravel, non è necessario scrivere un altro test per confermare che questo codice funzioni come previsto.public function test_render_menu () $ pages = array (); per ($ 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)); // Controlla se la cache è stata scritta $ this-> assertNotNull (Cache :: get ('pages_for_menu'));Questo è uno dei test più importanti per il
Pagina
modello. Innanzitutto, vengono create quattro pagine nelper
ciclo continuo. In seguito, il risultato delrenderMenu ()
la chiamata è memorizzata nel$ result
variabile. Questa variabile dovrebbe contenere una stringa HTML, contenente collegamenti alle pagine esistenti.Il
per ciascuno
loop controlla se lo slug (url) di ogni pagina è presente$ result
. Questo è sufficiente, dal momento che il formato esatto dell'HTML non è pertinente alle nostre esigenze.Alla fine, determiniamo se la chiave di cache,
pages_for_menu
, ha qualcosa memorizzato In altre parole, ha fatto ilrenderMenu ()
la chiamata ha effettivamente salvato un certo valore nella cache?public function test_clear_cache_after_save () // Un valore di test viene salvato nella cache Cache :: put ('pages_for_menu', 'avalue', 5); // Questo dovrebbe pulire il valore nella cache $ page = FactoryMuff :: create ('Pagina'); $ This-> assertNull (Cache :: get ( 'pages_for_menu'));Questo test ha lo scopo di verificare se, quando si salva un nuovo
Pagina
, la chiave della cache'Pages_for_menu'
è svuotato. IlFactoryMuff :: create ( 'Pagina');
alla fine attiva ilsalvare()
metodo, quindi dovrebbe essere sufficiente per la chiave,'Pages_for_menu'
, essere chiarito.funzione pubblica test_clear_cache_after_delete () $ page = FactoryMuff :: create ('Pagina'); // Un valore di test viene salvato nella cache Cache :: put ('pages_for_menu', 'value', 5); // Questo dovrebbe pulire il valore nella cache $ page-> delete (); $ This-> assertNull (Cache :: get ( 'pages_for_menu'));Simile al test precedente, questo determina se la chiave
'Pages_for_menu'
viene svuotato correttamente dopo aver cancellato aPagina
.Il tuo
PageTest.php
dovrebbe apparire così:assertEquals ($ page-> author_id, $ page-> author-> id); public function test_render_menu () $ pages = array (); per ($ 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)); // Controlla se la cache è stata scritta $ this-> assertNotNull (Cache :: get ('pages_for_menu')); public function test_clear_cache_after_save () // Un valore di test viene salvato nella cache Cache :: put ('pages_for_menu', 'avalue', 5); // Questo dovrebbe pulire il valore nella cache $ page = FactoryMuff :: create ('Pagina'); $ This-> assertNull (Cache :: get ( 'pages_for_menu')); public function test_clear_cache_after_delete () $ page = FactoryMuff :: create ('Pagina'); // Un valore di test viene salvato nella cache Cache :: put ('pages_for_menu', 'value', 5); // Questo dovrebbe pulire il valore nella cache $ page-> delete (); $ This-> assertNull (Cache :: get ( 'pages_for_menu'));Modello utente
Relativo ai modelli precedentemente presentati, ora abbiamo il
Utente
. Ecco il codice per quel modello:'string', 'email' => 'email', 'password' => '123123', 'password_confirmation' => '123123',); / ** * Ha molte pagine * / public function pages () return $ this-> hasMany ('Page', 'author_id'); / ** * Ha molti post * / public function posts () return $ this-> hasMany ('Post', 'author_id');Questo modello è assente nei test.
Possiamo osservare che, ad eccezione delle relazioni (che possono essere utili per testare), non c'è alcuna implementazione del metodo qui. Che dire dell'autenticazione? Bene, l'uso del pacchetto Confide fornisce già l'implementazione e i test per questo.
I test per
Zizaco \ Confide \ ConfideUser
si trovano in ConfideUserTest.php.È importante determinare le responsabilità della classe prima di scrivere i test. Testare l'opzione a "resettare la password" di una
Utente
sarebbe ridondante. Questo perché la corretta responsabilità di questo test è all'internoZizaco \ Confide \ ConfideUser
; Non inUtente
.Lo stesso vale per i test di convalida dei dati. Dato che il pacchetto, Ardent, gestisce questa responsabilità, non avrebbe molto senso testare di nuovo la funzionalità.
In breve: mantieni i tuoi test puliti e organizzati. Determina la corretta responsabilità di ogni classe e verifica solo ciò che è strettamente di sua competenza.
Conclusione
L'uso di un database in memoria è una buona pratica per eseguire rapidamente test su un database. Grazie all'aiuto di alcuni pacchetti, come Ardent, FactoryMuff e Confide, puoi ridurre al minimo la quantità di codice nei tuoi modelli, mantenendo i test puliti e obiettivi.
Nel seguito di questo articolo, esamineremo controllore test. Rimanete sintonizzati!
Iniziamo ancora con Laravel 4, insegniamoci l'essenziale!