Testarea paralelă pentru PHPUnit cu ParaTest

PHPUnit a sugerat paralelismul din 2007, dar, între timp, testele noastre continuă să meargă încet. Timpul este bani, nu? ParaTest este un instrument care se află pe partea de sus a PHPUnit și vă permite să efectuați teste în paralel fără a utiliza extensii. Acesta este un candidat ideal pentru testele funcționale (și anume seleniul) și alte procese de lungă durată.


ParaTest la serviciul tău

ParaTest este un instrument de linie de comandă robust pentru rularea testelor PHPUnit în paralel. Inspirat de oamenii buni de la Laboratoarele Sauce, acesta a fost inițial dezvoltat pentru a fi o soluție mai completă pentru îmbunătățirea vitezei testelor funcționale.

De la începuturile sale - și datorită unor colaboratori străluciți (inclusiv Giorgio Sironi, întreținător al extensiei PHPUnit Selenium) - ParaTest a devenit un instrument valoros pentru accelerarea testelor funcționale, precum și teste de integrare care implică baze de date, servicii web și sisteme de fișiere.

ParaTest are, de asemenea, onoarea de a fi asociat cu cadrul de testare Sauce Labs "Sausage", și a fost utilizat în aproape 7000 de proiecte, la momentul acestei scrieri.

Instalarea programului ParaTest

În prezent, singura modalitate oficială de a instala ParaTest este prin Composer. Pentru aceia dintre voi care sunt noi la compozitor, avem un articol extraordinar pe această temă. Pentru a prelua cea mai recentă versiune de dezvoltare, includeți următoarele în cadrul dvs. composer.json fişier:

 "necesită": "brianium / paratest": "dev-master"

Alternativ, pentru cea mai recentă versiune stabilă:

 "cer": "brianium / paratest": "0.4.4"

În continuare, fugiți compozitorul instala din linia de comandă. Binarul ParaTest va fi creat în furnizor / bin director.

Interfața ParaTest Command Line

ParaTest include o interfață de linie de comandă care ar trebui să fie familiară pentru majoritatea utilizatorilor PHPUnit - cu unele bonusuri adăugate pentru testarea paralelă.

Primul tău test paralel

Folosind ParaTest este la fel de simplu ca PHPUnit. Pentru a demonstra rapid acest lucru în acțiune, creați un director, paratest-eșantion, cu următoarea structură:

Să instalăm ParaTest așa cum am menționat mai sus. Presupunând că aveți o coajă Bash și un binar compozitor instalat global, puteți realiza acest lucru într-o singură linie de la paratest-eșantion director:

 echo '"necesită": "brianium / paratest": "0.4.4"'> composer.json && compune instala

Pentru fiecare dintre fișierele din director, creați o clasă de caz test cu același nume, cum ar fi:

 clasa SlowOneTest extinde PHPUnit_Framework_TestCase funcția publică test_long_running_condition () sleep (5); $ This-> assertTrue (true); 

Luați notă de utilizarea somn (5) pentru a simula un test care va dura cinci secunde pentru a executa. Așadar, ar trebui să avem cinci cazuri de testare care durează fiecare cinci secunde pentru a alerga. Folosind vanilie PHPUnit, aceste teste vor rula în serie și vor dura douăzeci și cinci de secunde, în total. ParaTest va executa aceste teste concomitent în cinci procese separate și ar trebui să dureze doar cinci secunde, nu douăzeci și cinci!

Acum, că avem o înțelegere a ceea ce este ParaTest, să săpăm puțin mai adânc în problemele asociate cu executarea testelor PHPUnit în paralel.


Problema la mână

Testarea poate fi un proces lent, mai ales atunci când începem să vorbim despre lovirea unei baze de date sau automatizarea unui browser. Pentru a testa mai repede și mai eficient, trebuie să fim capabili să executăm testele simultan (în același timp), în loc de serie (unul după altul).

Metoda generală pentru realizarea acestui lucru nu este o idee nouă: rulați diferite grupuri de test în mai multe procese PHPUnit. Acest lucru se poate realiza cu ușurință folosind funcția nativă PHP proc_open. Următoarele ar fi un exemplu în acest sens:

 / ** * $ runTests - procesele deschise în prezent * $ loadedTests - o serie de căi de testare * $ maxProcs - numărul total de procese pe care dorești să le executăm * / while (sizeof ($ runningTests) || sizeof ($ loadedTests) (sizeof ($ loadedTests) && sizeof ($ runTests) < $maxProcs) $runningTests[] = proc_open("phpunit " . array_shift($loadedTests), $descriptorspec, $pipes); //log results and remove any processes that have finished… 

Deoarece PHP nu are thread-uri native, aceasta este o metodă tipică pentru atingerea unui anumit nivel de concurrency. Provocările speciale ale instrumentelor de testare care utilizează această metodă pot fi fierte până la trei probleme de bază:

  • Cum vom încărca testele?
  • Cum putem agrega și raporta rezultatele din diferitele procese PHPUnit?
  • Cum putem asigura coerența cu instrumentul original (adică PHPUnit)?

Să ne uităm la câteva tehnici care au fost folosite în trecut și apoi să revedem ParaTest și cum diferă de restul mulțimii.


Cei care au venit înainte

După cum am menționat mai devreme, ideea de a rula PHPUnit în mai multe procese nu este una nouă. Procedura tipică folosită este ceva de-a lungul următoarelor linii:

  • Grep pentru metodele de testare sau încărcați un director de fișiere care conțin seturi de testare.
  • Deschideți un proces pentru fiecare metodă sau suită de testare.
  • Parse de ieșire de la conducta STDOUT.

Să aruncăm o privire la un instrument care folosește această metodă.

Bună, Paraunit

Paraunit a fost inițiatorul paralel original, alături de instrumentul Sauce Labs 'Sausage, care a servit ca punct de plecare pentru ParaTest. Să ne uităm la modul în care abordează cele trei probleme principale menționate mai sus.

Incarcarea testului

Paraunit a fost conceput pentru a ușura testarea funcțională. El execută fiecare metodă de testare mai degrabă decât o întreagă suită de test într-un proces PHPUnit propriu. Având în vedere calea spre o colecție de teste, Paraunit caută metode individuale de testare, prin potrivirea modelului cu conținutul fișierelor.

 preg_match_all ("/ function (test [^ \ (] +) \ (/", $ fileContents, $ meciuri);

Metodele de testare încărcate pot fi executate astfel:

 proc_open ("phpunit --filter = $ testName $ testFile", $ descriptorspec, $ pipes);

Într-un test în care fiecare metodă instalează și rupe un browser, acest lucru poate face lucrurile destul de repede, dacă fiecare dintre aceste metode este rulat într-un proces separat. Cu toate acestea, există o serie de probleme cu această metodă.

În timp ce metodele care încep cu cuvântul "Test,"este o convenție puternică printre utilizatorii PHPUnit, adnotările sunt o altă opțiune. Metoda de încărcare utilizată de Paraunit ar trece peste acest test perfect valid:

 / ** * @test * / funcția publică twoTodosCheckedShowsCorrectClearButtonText () $ this-> todos-> addTodos (array ('one', 'two')); $ This-> todos-> getToggleAll () -> clic (); $ this-> assertEquals ('Ștergeți 2 elemente completate', $ this-> todos-> getClearButton () -> text ()); 

Pe lângă faptul că nu susține adnotările de test, moștenirea este, de asemenea, limitată. S-ar putea argumenta meritele de a face ceva de genul asta, dar să luăm în considerare următoarele configurații:

 clasa abstractă TodoTest extinde PHPUnit_Extensions_Selenium2TestCase protejat $ browser = null; funcția publică setUp () // configure browser funcția publică testTypingIntoFieldAndHittingEnterAddsTodo () // selenium magic / ** * ChromeTodoTest.php * Nu există metode de testare pentru citire! * / clasa ChromeTodoTest extinde TodoTest protejat $ browser = 'chrome';  / ** * FirefoxTodoTest.php * Nu există metode de testare pentru a citi! * / clasa FirefoxTodoTest extinde TodoTest protejat $ browser = 'firefox'; 

Metodele moștenite nu sunt în fișier, deci nu vor fi încărcate niciodată.

Afișarea rezultatelor

Paraunit agregă rezultatele fiecărui proces prin analizarea rezultatului generat de fiecare proces. Această metodă permite companiei Paraunit să surprindă întreaga gamă de coduri scurte și feedback oferite de PHPUnit.

Dezavantajul la agregarea rezultatelor este că este destul de greu și ușor de rupt. Există multe rezultate diferite și multe expresii regulate la locul de muncă pentru a afișa rezultate semnificative în acest fel.

Coerența cu PHPUnit

Datorită file-ului grepping, Paraunit este destul de limitat în ceea ce privește caracteristicile PHPUnit pe care le poate suporta. Este un instrument excelent pentru a rula o structură simplă de teste funcționale, dar, pe lângă unele dintre dificultățile menționate deja, nu are suport pentru câteva funcții PHPUnit utile. Unele astfel de exemple includ seturi de testare, specificând fișierele de configurare și bootstrap, rezultatele înregistrării și rularea grupurilor de testare specifice.

Multe dintre instrumentele existente urmează acest model. Grepați un director de fișiere de testare și fie rulați întregul fișier într-un proces nou sau în fiecare metodă - niciodată la ambele.


ParaTest La Bat

Scopul programului ParaTest este de a sprijini testarea paralelă pentru o varietate de scenarii. Creat inițial pentru a umple golurile din Paraunit, acesta a devenit un instrument de linie de comandă robust pentru funcționarea simultană a suitelor de testare și a metodelor de testare. Acest lucru face ca ParaTest să fie un candidat ideal pentru teste de lungă durată de diferite forme și dimensiuni.

Cum ParaTest se ocupă de testarea paralelă

ParaTest se abate de la norma stabilită pentru a susține mai mult PHPUnit și acționează ca un candidat cu adevărat viabil pentru testarea în paralel.

Incarcarea testului

ParaTest încarcă testele în mod similar cu PHPUnit. Încarcă toate testele într - un director specificat care se termină cu * test.php sufix sau va încărca teste bazate pe fișierul de configurare standard PHPUnit XML. Încărcarea se realizează prin reflecție, astfel încât este ușor de susținut @Test metode, moștenire, seturi de testare și metode individuale de testare. Reflecția face ca adăugarea suportului pentru alte adnotări să fie rapidă.

Deoarece reflecția permite ParaTest să preia clasele și metodele, poate executa în paralel atât teste de testare, cât și metode de testare, făcându-i un instrument mai versatil.

ParaTest impune anumite constrângeri, dar cele bine întemeiate în comunitatea PHP. Testele trebuie să respecte standardul PSR-0 și sufixul fișierului implicit din * test.php nu este configurabil, așa cum este în PHPUnit. Există o ramură actuală în desfășurare pentru a susține aceeași configurație de sufix permisă în PHPUnit.

Afișarea rezultatelor

ParaTest se abate de la calea de partajare a țevilor STDOUT. În loc să parseze fluxurile de ieșire, ParaTest înregistrează rezultatele fiecărui proces PHPUnit în formatul JUnit și agregă rezultatele din aceste jurnale. Este mult mai ușor să citiți rezultatele testului dintr-un format stabilit decât un flux de ieșire.

        

Parcurgerea jurnalelor JUnit are unele dezavantaje minore. Testele ignorate și ignorate nu sunt raportate în feedback-ul imediat, dar vor fi reflectate în valorile totale afișate după un test.

Coerența cu PHPUnit

Reflecția permite ParaTest să susțină mai multe convenții PHPUnit. Consola ParaTest suportă mai multe caracteristici PHPUnit din cutie decât orice alt instrument similar, cum ar fi capacitatea de a rula grupuri, configurații de furnizare și fișiere bootstrap și rezultate jurnal în formatul JUnit.


Exemple para-Test

ParaTest poate fi folosit pentru a obține viteză în mai multe scenarii de testare.

Testarea funcțională cu seleniu

ParaTest excelează la testarea funcțională. Sprijină a -f comutați în consolă pentru a activa modul funcțional. Modul funcțional instruiește ParaTest să ruleze fiecare metodă de testare într-un proces separat, în loc de implicit, care este de a rula fiecare suită de testare într-un proces separat.

Este adesea cazul în care fiecare metodă de testare funcțională are mult de lucru, cum ar fi deschiderea unui browser, navigarea în jurul paginii și închiderea browserului.

Proiectul de exemplu, paratest-seleniu, demonstrează testarea unei aplicații Backbone.js todo cu Selenium și ParaTest. Fiecare metodă de testare deschide un browser și testează o caracteristică specifică:

 funcție publică setUp () $ this-> setBrowserUrl ('http://backbonejs.org/examples/todos/'); $ this-> todos = nou Todos ($ this-> prepareSession ());  funcția publică testTypingIntoFieldAndHittingEnterAddsTodo () $ this-> todos-> addTodo ("parallelize phpunit teste \ n"); $ this-> assertEquals (1, sizeof ($ acest-> todos-> getItems ()));  funcția publică testClickingTodoCheckboxMarksTodoDone () $ this-> todos-> addTodo ("asigurați-vă că puteți completa todos"); $ items = $ this-> todos-> getItems (); $ item = array_shift ($ elemente); $ This-> todos-> getItemCheckbox (element $) -> click (); $ this-> assertEquals ('done', $ item-> atribut ('class'));  // ... mai multe teste

Acest caz de test ar putea dura o secundă fierbinte dacă ar fi rulat în serie, prin intermediul vaniliei PHPUnit. De ce nu executați mai multe metode simultan?

Manipularea condițiilor de rasă

Ca și în cazul oricărei teste paralele, trebuie să avem în vedere scenariile care vor prezenta condițiile de rasă - cum ar fi mai multe procese care încearcă să acceseze o bază de date. Filiala Dev-master de la ParaTest are o caracteristică de testare foarte utilă, scrisă de colaboratorul Dimitris Baltas (dbaltas on Github), care face bazele de testare a integrării mult mai ușor.

Dimitris a inclus un exemplu util care demonstrează această caracteristică pe Github. În cuvintele lui Dimitris:

TEST_TOKEN încearcă să se ocupe de chestiunea resurselor comune într-un mod foarte simplu: să cloneze resursele pentru a se asigura că nici un proces concurent nu va avea acces la aceeași resursă.

A TEST_TOKEN variabila de mediu este furnizată pentru testele de consum și este reciclată după terminarea procesului. Acesta poate fi folosit pentru modificarea condiționată a testelor dvs., cum ar fi:

 funcția publică setUp () părinte :: setUp (); $ this -> _ nume fișier = sprintf ('out% s.txt', getenv ('TEST_TOKEN')); 

Laboratoarele ParaTest și Sos

Laboratorul de sosuri este Excaliburul testării funcționale. Sauce Labs oferă un serviciu care vă permite să testați cu ușurință aplicațiile dvs. într-o varietate de browsere și platforme. Dacă nu le-ați verificat înainte, vă încurajez foarte mult să faceți acest lucru.

Testarea cu Sauce ar putea fi un tutorial în sine, dar acești vrăjitori au făcut deja o treabă bună de a oferi tutoriale pentru utilizarea PHP și ParaTest pentru a scrie teste funcționale utilizând serviciul lor.


Viitorul ParaTest

ParaTest este un instrument excelent pentru a completa unele dintre lacunele PHPUnit, dar, în cele din urmă, este doar un plug în baraj. Un scenariu mult mai bun ar fi sprijinul nativ în PHPUnit!

Între timp, ParaTest va continua să crească sprijinul pentru mai mult comportament nativ al PHPUnit. Acesta va continua să ofere caracteristici care sunt utile pentru testarea paralelă - în special în domeniile funcționale și de integrare.

ParaTest are multe lucruri minunate în lucrări pentru a îmbunătăți transparența dintre PHPUnit și ea însăși, în primul rând în ce opțiuni de configurare sunt suportate.

Ultima versiune stabilă de ParaTest (v0.4.4) suportă confortabil Mac, Linux și Windows, însă există câteva solicitări și caracteristici valoroase de tragere în dev-master care să răspundă cu siguranță mulțimilor Mac și Linux. Așa că va fi o conversație interesantă.

Citire și resurse suplimentare

Există o mulțime de articole și resurse pe web ce conțin ParaTest. Dați-le o citire, dacă vă interesează:

  • ParaTest pe Github
  • Paralel PHPUnit de către ParaTest contribuitor și PHPUnit Selenium susținător de extensie Giorgio Sironi
  • Contribuția la Paratest. Un articol excelent despre experimentul WrapperRunner al lui Giorgio pentru ParaTest
  • Giorgio's WrapperRunner Cod sursă
  • tripsta / paratest-probă. Un exemplu de caracteristică TEST_TOKEN creat de acesta este Dimitris Baltas
  • brianium / paratest-seleniu. Un exemplu de folosire a ParaTest pentru a scrie teste funcționale
Cod