Paralleltesting for PHPUnit med ParaTest

PHPUnit har antydet parallellitet siden 2007, men i mellomtiden fortsetter testene våre sakte. Tiden er penger, ikke sant? ParaTest er et verktøy som sitter på toppen av PHPUnit og lar deg kjøre tester parallelt uten bruk av utvidelser. Dette er en ideell kandidat for funksjonelle (dvs. Selen) tester og andre langvarige prosesser.


ParaTest på din tjeneste

ParaTest er et robust kommandolinjeverktøy for å kjøre PHPUnit-tester parallelt. Inspirert av de fine folkene på Sauce Labs, ble det opprinnelig utviklet for å være en mer komplett løsning for å forbedre hastigheten på funksjonelle tester.

Siden starten - og takket være noen strålende bidragsytere (inkludert Giorgio Sironi, vedlikeholderen av PHPUnit Selen-utvidelsen) - ParaTest er blitt et verdifullt verktøy for å øke funksjonstester, samt integreringstester som involverer databaser, webtjenester og filsystemer.

ParaTest har også ære for å bli buntet med Sauce Labs 'testing framework Sausage, og har blitt brukt i nesten 7000 prosjekter, på tidspunktet for denne skrivingen.

Installere ParaTest

For øyeblikket er den eneste offisielle måten å installere ParaTest gjennom Composer. For de av dere som er nye for Composer, har vi en flott artikkel om emnet. For å hente den nyeste utviklingsversjonen, ta med følgende i din composer.json fil:

 "krever": "brianium / paratest": "dev-master"

Alternativt, for den siste stabile versjonen:

 "krever": "brianium / paratest": "0.4.4"

Deretter løp komponent installasjon fra kommandolinjen. ParaTest-binæret vil bli opprettet i leverandør / bin katalog.

ParaTest-kommandolinjegrensesnittet

ParaTest inkluderer et kommandolinjegrensesnitt som bør være kjent for de fleste PHPUnit-brukere - med noen ekstra bonuser for parallell testing.

Din første parallelle test

Bruke ParaTest er like enkelt som PHPUnit. For å raskt vise dette i aksjon, opprett en katalog, paratest-prøve, med følgende struktur:

La oss installere ParaTest som nevnt ovenfor. Forutsatt at du har et Bash-skall og en globalt installert Composer-binær, kan du oppnå dette i en linje fra paratest-prøve katalogen:

 ekko '("krever": "brianium / paratest": "0.4.4"'> composer.json && composer install

For hver av filene i katalogen, opprett en testcase klassen med samme navn, slik som:

 klasse SlowOneTest utvider PHPUnit_Framework_TestCase offentlig funksjon test_long_running_condition () sleep (5); $ Dette-> assertTrue (true); 

Vær oppmerksom på bruken av søvn (5) å simulere en test som vil ta fem sekunder å utføre. Så vi burde ha fem testfall som hver tar fem sekunder å løpe. Ved hjelp av vanilla PHPUnit, vil disse testene løpe serielt og ta tjuefem sekunder totalt. ParaTest vil kjøre disse testene samtidig i fem separate prosesser og bør bare ta fem sekunder, ikke tjuefem!

Nå som vi har en forståelse av hva ParaTest er, la oss grave litt dypere inn i problemene knyttet til å kjøre PHPUnit-tester parallelt.


Problemet ved hånden

Testing kan være en sakte prosess, spesielt når vi begynner å snakke om å slå en database eller automatisere en nettleser. For å teste raskere og mer effektivt, må vi kunne kjøre testene våre samtidig (samtidig), i stedet for serielt (den ene etter den andre).

Den generelle metoden for å oppnå dette er ikke en ny ide: Kjør forskjellige testgrupper i flere PHPUnit-prosesser. Dette kan enkelt oppnås ved hjelp av den native PHP-funksjonen proc_open. Følgende ville være et eksempel på dette i aksjon:

 / ** * $ runningTests - for øyeblikket åpne prosesser * $ loadedTests - en rekke testbaner * $ maxProcs - totalt antall prosesser vi vil kjøre * / mens (sizeof ($ runningTests) || sizeof ($ loadedTests)) mens (sizeof ($ loadedTests) && sizeof ($ runningTests) < $maxProcs) $runningTests[] = proc_open("phpunit " . array_shift($loadedTests), $descriptorspec, $pipes); //log results and remove any processes that have finished… 

Fordi PHP mangler innfødte tråder, er dette en typisk metode for å oppnå noe nivå av samtidighet. Spesielle utfordringer ved testing av verktøy som bruker denne metoden kan kokes ned til tre kjerneproblemer:

  • Hvordan laster vi inn tester?
  • Hvordan samler vi og rapporterer resultater fra de ulike PHPUnit-prosessene?
  • Hvordan kan vi gi konsistens med det opprinnelige verktøyet (dvs. PHPUnit)?

La oss se på noen teknikker som tidligere har vært ansatt, og deretter gjennomgå ParaTest og hvordan det skiller seg fra resten av publikum..


De som kom før

Som nevnt tidligere er ideen om å kjøre PHPUnit i flere prosesser ikke en ny. Den typiske prosedyren som brukes er noe langs de følgende linjene:

  • Grep for testmetoder eller laste inn en katalog med filer som inneholder testpakker.
  • Åpne en prosess for hver testmetode eller suite.
  • Parse utgang fra STDOUT-rør.

La oss ta en titt på et verktøy som benytter denne metoden.

Hei, parade

Paraunit var den opprinnelige parallelle løperen bundtet med Sauce Labs 'Sausage tool, og det tjente som utgangspunkt for ParaTest. La oss se på hvordan det takler de tre hovedproblemene som er nevnt ovenfor.

Testinnlasting

Paraunit ble designet for å lette funksjonell testing. Den utfører hver testmetode i stedet for en hel testpakke i en egen PHPUnit-prosess. Gitt vei til en samling av tester, søker Paraunit etter individuelle testmetoder, via mønstermatching mot filinnhold.

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

Lastede testmetoder kan deretter kjøre slik:

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

I en test hvor hver metode er å sette opp og rive ned en nettleser, kan dette gjøre ting ganske raskere, hvis hver av disse metodene kjøres i en separat prosess. Det er imidlertid et par problemer med denne metoden.

Mens metoder som begynner med ordet, "test,"er en sterk konvensjon blant PHPUnit-brukere, annoteringer er et annet alternativ. Lastingsmetoden som brukes av Paraunit, vil hoppe over denne helt gyldige testen:

 / ** * @test * / offentlig funksjon twoTodosCheckedShowsCorrectClearButtonText () $ this-> todos-> addTodos (array ('en', 'two')); $ Dette-> todos-> getToggleAll () -> klikk (); $ this-> assertEquals ('Clear 2 completed items', $ this-> todos-> getClearButton () -> tekst ()); 

I tillegg til ikke å støtte testannonser, er arv også begrenset. Vi kan argumentere for fordelene ved å gjøre noe slikt, men la oss vurdere følgende oppsett:

 abstrakt klasse TodoTest utvider PHPUnit_Extensions_Selenium2TestCase protected $ browser = null; offentlig funksjon setUp () // configure browser offentlig funksjon testTypingIntoFieldAndHittingEnterAddsTodo () // selenium magic / ** * ChromeTodoTest.php * Ingen testmetoder å lese! * / klasse ChromeTodoTest utvider TodoTest protected $ browser = 'chrome';  / ** * FirefoxTodoTest.php * Ingen testmetoder å lese! * / klasse FirefoxTodoTest utvider TodoTest protected $ browser = 'firefox'; 

De arvede metodene er ikke i filen, så de blir aldri lastet.

Viser resultater

Parametere aggregerer resultatene av hver prosess ved å analysere utgangen generert av hver prosess. Denne metoden tillater paraunit å fange hele spekteret av korte koder og tilbakemelding presentert av PHPUnit.

Ulempen med å aggregere resultater på denne måten er at den er ganske uhåndterlig og lett å bryte. Det er mange forskjellige utfall å regne for, og mange regelmessige uttrykk på jobb for å vise meningsfulle resultater på denne måten.

Konsistens med PHPUnit

På grunn av filgrepingen er Paraunit ganske begrenset i hvilke PHPUnit funksjoner den kan støtte. Det er et utmerket verktøy for å kjøre en enkel struktur av funksjonelle tester, men i tillegg til noen av de nevnte vanskelighetene mangler det støtte for noen nyttige PHPUnit-funksjoner. Noen av disse eksemplene inkluderer testpakker, spesifisering av konfigurasjons- og oppstartsfiler, loggingsresultater og kjøring av bestemte testgrupper.

Mange av de eksisterende verktøyene følger dette mønsteret. Grep en katalog av testfiler og kjør hele filen i en ny prosess eller hver metode - aldri begge.


ParaTest At Bat

Målet med ParaTest er å støtte parallell testing for en rekke scenarier. Originalt opprettet for å fylle hullene i paraunit, har det blitt et robust kommandolinjeverktøy for å kjøre begge testpakker og testmetoder parallelt. Dette gjør ParaTest til en ideell kandidat for lange løpende tester av forskjellige former og størrelser.

Hvordan ParaTest håndterer parallell testing

ParaTest avviker fra den etablerte normen for å støtte mer av PHPUnit, og fungerer som en virkelig levedyktig kandidat for parallell testing.

Testinnlasting

ParaTest laster tester på samme måte som PHPUnit. Den laster alle tester i en spesifisert katalog som slutter med * test.php suffiks, eller vil laste tester basert på standard PHPUnit XML konfigurasjonsfil. Lasting er oppnådd, via refleksjon, så det er lett å støtte @test metoder, arv, testpakker og individuelle testmetoder. Refleksjon gjør det enklere å legge til støtte for andre merknader.

Fordi refleksjon lar ParaTest få tak i klasser og metoder, kan den kjøre begge testsuiter og testmetoder parallelt, noe som gjør det til et mer allsidig verktøy.

ParaTest pålegger noen begrensninger, men velbegrunnede i PHP-fellesskapet. Testene må følge PSR-0-standarden, og standardfilens suffiks av * test.php kan ikke konfigureres, som det er i PHPUnit. Det er en gjeldende filial i gang for å støtte samme suffiks konfigurasjon tillatt i PHPUnit.

Viser resultater

ParaTest avviker også fra banen til å analysere STDOUT-rør. I stedet for å analysere utgangsstrømmer, logger ParaTest resultatene av hver PHPUnit-prosess i JUnit-formatet og aggregerer resultater fra disse loggene. Det er mye lettere å lese testresultater fra et etablert format enn en utgangsstrøm.

        

Parsing JUnit logger har noen mindre ulemper. Hoppet over og ignorert tester rapporteres ikke i umiddelbar tilbakemelding, men de blir reflektert i de totale verdiene som vises etter en testkjøring.

Konsistens med PHPUnit

Refleksjon lar ParaTest støtte flere PHPUnit-konvensjoner. ParaTest-konsollen støtter flere PHPUnit-funksjoner ut av boksen enn noe annet lignende verktøy, for eksempel muligheten til å kjøre grupper, levere konfigurasjoner og bootstrap-filer og logge resultater i JUnit-formatet.


ParaTest-eksempler

ParaTest kan brukes til å få fart i flere testscenarier.

Funksjonell testing med selen

ParaTest utmerker seg ved funksjonell testing. Den støtter a -f bytt i konsollen for å aktivere funksjonell modus. Funksjonell modus instruerer ParaTest til å kjøre hver testmetode i en separat prosess, i stedet for standard, som skal kjøre hver testpakke i en separat prosess.

Det er ofte slik at hver funksjonell testmetode gjør mye arbeid, for eksempel å åpne en nettleser, navigere rundt på siden og deretter lukke nettleseren.

Eksempelprosjektet, paratest-selenium, demonstrerer testing av et Backbone.js todo-program med Selen og ParaTest. Hver testmetode åpner en nettleser og tester en bestemt funksjon:

 offentlig funksjon setUp () $ this-> setBrowserUrl ('http://backbonejs.org/examples/todos/'); $ this-> todos = new Todos ($ this-> prepareSession ());  offentlig funksjon testTypingIntoFieldAndHittingEnterAddsTodo () $ this-> todos-> addTodo ("parallelliser fiksitester \ n"); $ this-> assertEquals (1, sizeof ($ this-> todos-> getItems ()));  offentlig funksjon testClickingTodoCheckboxMarksTodoDone () $ this-> todos-> addTodo ("pass på at du kan fullføre todos"); $ items = $ this-> todos-> getItems (); $ item = array_shift ($ items); $ Dette-> todos-> getItemCheckbox ($ element) -> klikk (); $ this-> assertEquals ('done', $ item-> attributt ('class'));  // ... flere tester

Dette testfallet kan ta et varmt sekund hvis det skulle løpe serielt, via vanilla PHPUnit. Hvorfor ikke kjøre flere metoder samtidig?

Håndtering av rasebetingelser

Som med enhver parallell testing må vi være oppmerksom på scenarier som vil presentere raseforhold - som for eksempel flere prosesser som prøver å få tilgang til en database. Dev-master-grenen av ParaTest-sporten er en veldig praktisk testtegnfunksjon, skrevet av samarbeidspartner Dimitris Baltas (dbaltas on Github), som gjør integrasjonstesting av databaser mye lettere.

Dimitris har tatt med et nyttig eksempel som demonstrerer denne funksjonen på Github. I Dimitris egne ord:

TEST_TOKEN forsøker å håndtere felles ressurs problemet på en svært enkel måte: klone ressursene for å sikre at ingen samtidige prosesser vil få tilgang til samme ressurs.

EN TEST_TOKEN Miljøvariabel er gitt for tester for å konsumere, og resirkuleres når prosessen er ferdig. Det kan brukes til å betinget endre tester, slik som:

 offentlig funksjon setUp () foreldre :: setUp (); $ dette -> _ filnavn = sprintf ('ut% s.txt', getenv ('TEST_TOKEN')); 

ParaTest og Sauce Labs

Sauce Labs er Excalibur for funksjonell testing. Sauce Labs tilbyr en tjeneste som lar deg enkelt teste dine applikasjoner i en rekke nettlesere og plattformer. Hvis du ikke har sjekket dem ut før, anbefaler jeg sterkt at du gjør det.

Testing med saus kan være en opplæring i seg selv, men de veiviserne har allerede gjort en god jobb med å gi opplæringsprogrammer for bruk av PHP og ParaTest for å skrive funksjonelle tester ved hjelp av deres tjeneste.


Future of ParaTest

ParaTest er et flott verktøy for å fylle noen av hullene i PHPUnit, men til slutt er det bare en plugg i dammen. Et mye bedre scenario ville være innfødt støtte i PHPUnit!

I mellomtiden vil ParaTest fortsette å øke støtten til mer av PHPUnits opprinnelige oppførsel. Det vil fortsette å tilby funksjoner som er nyttige for parallell testing - spesielt i funksjonelle og integreringsområder.

ParaTest har mange gode ting i arbeidet med å bøte opp gjennomsiktigheten mellom PHPUnit og seg selv, først og fremst i hvilke konfigurasjonsalternativer som støttes.

Den nyeste stabile versjonen av ParaTest (v0.4.4) støtter komfortabelt Mac, Linux og Windows, men det er noen verdifulle trekkforespørsler og funksjoner i dev-mester som absolutt imøtekommer Mac og Linux folkemengdene. Så det vil være en interessant samtale fremover.

Ekstra lesing og ressurser

Det finnes en håndfull artikler og ressurser rundt på nettet som har ParaTest. Gi dem en les, hvis du er interessert:

  • ParaTest på Github
  • Parallel PHPUnit av ParaTest bidragsyter og PHPUnit Seleni forlengelse vedlikeholder Giorgio Sironi
  • Bidrar til Paratest. En utmerket artikkel om Giorgios eksperimentelle WrapperRunner for ParaTest
  • Giorgio's WrapperRunner Kildekode
  • tripsta / paratest-prøven. Et eksempel på TEST_TOKEN-funksjonen av det er skaperen Dimitris Baltas
  • brianium / paratest-selen. Et eksempel på å bruke ParaTest til å skrive funksjonelle tester