Prova il tuo PHP Codebase con EnhancePHP

Lo sai; Lo so. Dovremmo testare il nostro codice più di quanto facciamo noi. Parte del motivo per cui non lo facciamo, penso, è che non sappiamo esattamente come. Bene, mi sto liberando di quella scusa oggi: ti sto insegnando a testare il tuo PHP con il framework EnhancePHP.


Incontra EnhancePHP

Non ho intenzione di provare a convincerti a testare il tuo codice; e non discuteremo nemmeno di Test Driven Development. È già stato fatto prima su Nettuts +. In questo articolo, Nikko Bautista spiega esattamente perché il test è una buona cosa e delinea un flusso di lavoro TDD. Leggilo prima o poi, se non hai familiarità con TDD. Usa anche la libreria SimpleTest per i suoi esempi, quindi se non ti piace l'aspetto di EnhancePHP, potresti provare SimpleTest come alternativa.

Come ho detto, useremo EnhancePHP. È una piccola piccola libreria PHP, un singolo file, che offre molte funzionalità di test.

Inizia andando alla loro pagina di download e afferrando l'ultima versione del framework.

Stiamo andando a costruire una classe di validazione davvero semplice da testare. Non farà troppo: basta tornare vero se l'articolo supera la convalida, o falso se non lo fa. Quindi, imposta un piccolo progetto davvero semplice:

Lo faremo in modo semi-TDD, quindi iniziamo scrivendo alcuni test.


Test di scrittura

La piccola classe sta andando a convalidare tre cose: indirizzi e-mail, nomi utente e numeri di telefono.

Ma prima di scrivere dei test effettivi, dovremo impostare la nostra classe:

  val = new Validation (); 

Questo è il nostro inizio; nota che stiamo estendendo la classe \ Migliora \ TestFixture. Facendo ciò, lasciamo che EnhancePHP sappia che tutti i metodi pubblici di questa classe sono test, con l'eccezione dei metodi impostare e demolire. Come puoi immaginare, questi metodi vengono eseguiti prima e dopo tutti i test (non prima e dopo ciascuno di essi). In questo caso, il nostro impostare il metodo creerà un nuovo Validazione istanza e assegnarlo a una proprietà sulla nostra istanza.

A proposito, se sei relativamente nuovo a PHP, potresti non esserne familiare \ Migliora \ TestFixture sintassi: che cosa è con le barre? Questo è PHP namespacing per te; controlla i documenti se non hai familiarità con esso.

Quindi, i test!

Indirizzi email

Iniziamo convalidando gli indirizzi email. Come vedrai, basta fare un test di base è piuttosto semplice:

 funzione pubblica validates_a_good_email_address () $ result = $ this-> val-> validate_email ("[email protected]"); \ Migliorare \ Assert :: IsTrue ($ risultato); 

Chiamiamo semplicemente il metodo che vogliamo testare, passandogli un indirizzo email valido e archiviando il file $ result. Quindi, ci consegniamo $ result al è vero metodo. Questo metodo appartiene al \ Migliora \ Assert classe.

Vogliamo assicurarci che la nostra classe respinga gli indirizzi non email. Quindi, proviamo per quello:

 public function reject_bad_email_addresses () $ val_wrapper = \ Enhance \ Core :: getCodeCoverageWrapper ('Validation'); $ val_email = $ this-> get_scenario ('validate_email'); $ address = array ("john", "[email protected]", "john @ doe.", "jo*[email protected]"); foreach ($ indirizzi come $ addr) $ val_email-> con ($ addr) -> expect (false);  $ val_email-> verifyExpectations (); 

Questo introduce una caratteristica piuttosto interessante di EnhancePHP: scenari. Vogliamo testare una serie di indirizzi non email per assicurarci che il nostro metodo possa tornare falso. Creando uno scenario, essenzialmente avvolgiamo un'istanza della nostra classe in alcune qualità di EnhancePHP, scriviamo molto meno codice per testare tutti i nostri non-indirizzi. Questo è ciò che $ val_wrapper è: un'istanza modificata del nostro Validazione classe. Poi, $ val_email è l'oggetto scenario, un po 'come una scorciatoia per il validate_email metodo.

Quindi, abbiamo una serie di stringhe che non devono essere convalidate come indirizzi email. Effettueremo un ciclo su quell'array con a per ciascuno ciclo continuo. Nota come eseguiamo il test: chiamiamo il con metodo sul nostro oggetto scenario, passandogli i parametri per il metodo che stiamo testando. Quindi, chiamiamo il aspettarsi metodo su quello, e passarlo qualunque cosa ci aspettiamo di tornare.

Infine, chiamiamo lo scenario verifyExpectations metodo.

Quindi, i primi test sono scritti; come li eseguiamo?


Esecuzione di test

Prima di eseguire effettivamente i test, dovremo creare il nostro Validazione classe. Dentro lib.validation.php, inizia con questo:

  

Ora in test.php, ci metteremo tutto insieme:

  

Innanzitutto, richiederemo tutti i file necessari. Quindi, chiamiamo il runTests metodo, che trova i nostri test.

Poi arriva la parte pulita. Avvia un server e otterrai un bel output HTML:

Molto carino, vero? Ora, se hai PHP nel tuo terminale, esegui questo nel terminale:

EnhancePHP nota che ci si trova in un altro ambiente e si adatta in modo appropriato all'output. Un vantaggio secondario di questo è che se si utilizza un IDE, come PhpStorm, che può eseguire test unitari, è possibile visualizzare questo output del terminale direttamente all'interno dell'IDE.

Puoi anche ottenere output XML e TAP, se è quello che preferisci, basta passare \ Migliorare \ TemplateType :: Xml o \ Migliorare \ TemplateType :: Tap al runTests metodo per ottenere l'output appropriato. Nota che eseguirlo nel terminale produrrà anche i risultati della riga di comando, indipendentemente da cosa passi runTests.

Ottenere i test per passare

Scriviamo il metodo che fa passare i nostri test. Come sai, questo è il validate_email. Nella parte superiore del Validazione classe, definiamo una proprietà pubblica:

 public $ email_regex = '/^[\w+-_\ .]+@[\w\.]+\.\w+$/';

Sto mettendo questo in una proprietà pubblica in modo che se l'utente vuole sostituirlo con la loro espressione regolare, potrebbero. Sto usando questa semplice versione di una regex di posta elettronica, ma puoi sostituirla con la tua espressione regolare preferita, se lo desideri.

Quindi, c'è il metodo:

 funzione pubblica validate_email ($ indirizzo) return preg_match ($ this-> email_regex, $ indirizzo) == 1

Ora eseguiamo di nuovo i test e:


Scrivere più test

Tempo per ulteriori test:

Nomi utente

Creiamo ora alcuni test per i nomi utente. I nostri requisiti sono semplicemente che deve essere una stringa di caratteri da 4 a 20 che consiste solo di caratteri o periodi di parole. Così:

 funzione pubblica validates_a_good_username () $ result = $ this-> val-> validate_username ("some_user_name.12"); \ Migliorare \ Assert :: IsTrue ($ risultato); 

Ora, che ne dici di alcuni nomi utente che non dovrebbero essere convalidati:

 public function rejects_bad_usernames () $ val_username = $ this-> get_scenario ('validate_username'); $ usernames = array ("nome con spazio", "no! exclaimation! mark", "ts", "thisUsernameIsTooLongItShouldBeBetweenFourAndTwentyCharacters"); foreach ($ nomi utente come $ nome) $ val_username-> con ($ name) -> expect (false);  $ val_username-> verifyExpectations (); 

Questo è molto simile al nostro reject_bad_email_addresses funzione. Si noti, tuttavia, che stiamo chiamando questo get_scenario metodo: da dove viene? Sto astragendo le funzionalità di creazione degli scenari in metodo privato, in fondo alla nostra classe:

 funzione privata get_scenario ($ method) $ val_wrapper = \ Enhance \ Core :: getCodeCoverageWrapper ('Validation'); return \ Enhance \ Core :: getScenario ($ val_wrapper, $ method); 

Possiamo usare questo nel nostro reject_bad_usernames e sostituire la creazione dello scenario in reject_bad_email_addresses anche. Poiché questo è un metodo privato, EnhancePHP non tenterà di eseguirlo come un normale test, come con metodi pubblici.

Faremo passare questi test in modo simile a come abbiamo fatto il primo set pass:

 # In alto ... public $ username_regex = '/^[\w\.]4,20$/'; # e il metodo ... public function validate_username ($ username) return preg_match ($ this-> username_regex, $ username) == 1; 

Questo è piuttosto semplice, ovviamente, ma questo è tutto ciò che è necessario per raggiungere il nostro obiettivo. Se volessimo restituire una spiegazione in caso di fallimento, potresti fare qualcosa del genere:

 funzione pubblica validate_username ($ username) $ len = strlen ($ username); se ($ len < 4 || $len > 20) return "Il nome utente deve essere compreso tra 4 e 20 caratteri";  elseif (preg_match ($ this-> username_regex, $ username) == 1) return true;  else return "Il nome utente deve contenere solo lettere, numeri, trattini bassi o punti."; 

Certo, potresti anche voler controllare se il nome utente esiste già.

Ora esegui i test e dovresti vederli tutti che passano.

Numeri di telefono

Penso che tu stia imparando da ora, quindi finiamo il nostro esempio di validazione controllando i numeri di telefono:

 funzione pubblica validates_good_phonenumbers () $ val_phonenumber = $ this-> get_scenario ("validate_phonenumber"); $ numbers = array ("1234567890", "(890) 123-4567", "123-456-7890", "123 456 7890", "(123) 456 7890"); foreach ($ num as $ num) $ val_phonenumber-> with ($ num) -> expect (true);  $ val_phonenumber-> verifyExpectations ();  public function rejects_bad_phonenumnbers () $ result = $ this-> val-> validate_phonenumber ("123456789012"); \ Migliorare \ Assert :: IsFalse ($ risultato); 

Probabilmente puoi capire il Validazione metodo:

 public $ phonenumber_regex = '/ ^ \ d 10 $ | ^ (\ (? \ d 3 \)? [| -] \ d 3 [| -] \ d 4) $ /'; public function validate_phonenumber ($ number) return preg_match ($ this-> phonenumber_regex, $ number) == 1; 

Ora possiamo eseguire tutti i test insieme. Ecco come appare dalla riga di comando (il mio ambiente di test preferito):


Altre funzionalità di prova

Ovviamente, EnhancePHP può fare molto di più di quello che abbiamo visto in questo piccolo esempio. Diamo un'occhiata ad alcuni di questo ora.

Abbiamo incontrato brevemente il \ Migliora \ Assert classe nel nostro primo test. In realtà non lo usavamo altrimenti, perché non è utile quando si utilizzano gli scenari. Tuttavia, è dove sono tutti i metodi di asserzione. La bellezza di loro è che i loro nomi rendono la loro funzionalità incredibilmente ovvia. I seguenti esempi di test passeranno:

  • \ Enhance \ Assert :: areIdentical ("Netsuts +", "Netsuts +")
  • \ Enhance \ Assert :: areNotIdentical ("Netsuts +", "Psdtuts +")
  • \ Migliorare \ Assert :: IsTrue (vero)
  • \ Migliorare \ Assert :: IsFalse (false)
  • \ Enhance \ Assert :: contains ("Net", "Netsuts +")
  • \ Migliorare \ Assert :: isNull (null)
  • \ Migliorare \ Assert :: isNotNull ( 'Nettust +')
  • \ Enhance \ Assert :: isInstanceOfType ('Exception', new Exception (""))
  • \ Enhance \ Assert :: isNotInstanceOfType ('String', new Exception (""))

Ci sono anche altri metodi di asserzione; è possibile controllare i documenti per un elenco completo ed esempi.

Mocks

EnhancePHP può anche fare mock e stub. Non hai mai sentito parlare di motti e tronconi? Beh, non sono troppo complicati. Un mock è un wrapper per oggetto, che può tenere traccia di quali sono i metodi chiamati, con quali proprietà vengono chiamate e quali valori vengono restituiti. Una finta avrà qualche test da verificare, come vedremo.

Ecco un piccolo esempio di simulazione. Iniziamo con una classe molto semplice che conta:

 num = $ this-> num + $ num; restituire $ this-> num; 

Abbiamo una funzione: incremento, accetta un parametro (ma il valore predefinito è 1) e incrementa il valore $ num proprietà per quel numero.

Potremmo usare questa classe se stessimo costruendo un quadro di valutazione:

 class scoreboard public $ home = 0; pubblico $ away = 0; funzione pubblica __construct ($ home, $ away) $ this-> home_counter = $ home; $ this-> away_counter = $ away;  public function score_home () $ this-> home = $ this-> home_counter-> increment (); restituire $ this-> home;  public function score_away () $ this-> away = $ this-> away_counter-> increment (); restituire $ this-> home; 

Ora vogliamo testare per assicurarci che il contatore metodo di istanza incremento funziona correttamente quando il tabellone segnapunti i metodi di istanza lo chiamano. Quindi creiamo questo test:

 class ScoreboardTest estende \ Enhance \ TestFixture public function score_home_calls_increment () $ home_counter_mock = \ Enhance \ MockFactory :: createMock ("Counter"); $ away_counter = new Counter (); $ home_counter_mock-> addExpectation (\ Enhance \ Expect :: method ('increment')); $ scoreboard = new Scoreboard ($ home_counter_mock, $ away_counter); $ Scoreboard-> score_home (); $ Home_counter_mock-> verifyExpectations ();  \ Enhance \ Core :: runTests ();

Si noti che iniziamo creando $ home_counter_mock: usiamo il factory di simulazione EnhancePHP, passandogli il nome della classe che stiamo deridendo. Questo restituisce un'istanza "avvolta" di contatore. Quindi, aggiungiamo un'aspettativa, con questa linea

 $ home_counter_mock-> addExpectation (\ Enhance \ Expect :: method ('increment'));

La nostra aspettativa dice solo che ci aspettiamo il incremento metodo da chiamare.

Dopodiché, continuiamo a creare il tabellone segnapunti istanza e chiamata score_home. Quindi, noi verifyExpectations. Se lo esegui, vedrai che il nostro test è scaduto.

Potremmo anche indicare quali parametri vogliamo che un metodo sull'oggetto mock venga chiamato con, quale valore viene restituito, o quante volte deve essere chiamato il metodo, con qualcosa di simile a questo:

 $ home_counter_mock-> addExpectation (\ Enhance \ Expect :: method ('increment') -> with (10)); $ home_counter_mock-> addExpectation (\ Enhance \ Expect :: method ('increment') -> times (2)); $ home_counter_mock-> addExpectation (\ Enhance \ Expect :: method ('increment') -> restituisce (1)); $ home_counter_mock-> addExpectation (\ Enhance \ Expect :: method ('increment') -> con (3) -> times (1)); $ home_counter_mock-> addExpectation (\ Enhance \ Expect :: method ('increment') -> with (2) -> returns (2));

Dovrei dirlo, mentre con e volte mostrerà test falliti se le aspettative non sono intese, ritorna non lo fa. Dovrai memorizzare il valore restituito e usare un'affermazione per questo. Non sono sicuro del perché questo sia il caso, ma ogni biblioteca ha le sue stranezze :). (Puoi vedere un esempio di questo negli esempi di librerie in Github.)

stubs

Quindi, ci sono mozziconi. Uno stub si riempie per un oggetto e un metodo reali, restituendo esattamente ciò che gli dici. Quindi, diciamo che vogliamo essere sicuri che il nostro tabellone segnapunti l'istanza sta usando correttamente il valore da cui riceve incremento, possiamo mozzare a contatore istanza in modo che possiamo controllare cosa incremento tornerà:

 class ScoreboardTest estende \ Enhance \ TestFixture public function score_home_calls_increment () $ home_counter_stub = \ Enhance \ StubFactory :: createStub ("Counter"); $ away_counter = new Counter (); $ home_counter_stub-> addExpectation (\ Enhance \ Expect :: method ('incrementa') -> restituisce (10)); $ scoreboard = new Scoreboard ($ home_counter_stub, $ away_counter); $ result = $ scoreboard-> score_home (); \ Enhance \ Assert :: areIdentical ($ result, 10);  \ Enhance \ Core :: runTests ();

Qui, stiamo usando \ Migliorare \ StubFactory :: createStub per creare il nostro contatore stub. Quindi, aggiungiamo un'aspettativa al metodo incremento restituirà 10. Possiamo vedere che il risultato è ciò che ci aspetteremmo, dato il nostro codice.

Per altri esempi di mock e stub con la libreria EnhancePHP, controlla Github Repo.


Conclusione

Bene, questo è uno sguardo ai test in PHP, usando il framework EnhancePHP. È un framework incredibilmente semplice, ma fornisce tutto ciò che è necessario per eseguire alcuni semplici test di unità sul codice PHP. Anche se scegli un metodo / framework diverso per testare il tuo PHP (o forse fai il tuo giro!), Spero che questo tutorial abbia suscitato interesse nel testare il tuo codice e quanto possa essere semplice.

Ma forse hai già provato il tuo PHP. Facci sapere cosa usi nei commenti; dopotutto, siamo tutti qui per imparare gli uni dagli altri! Grazie mille per esserti fermato!