Batjocura o cale mai buna

Batjocura este o extensie PHP care oferă o experiență de batjocură superioară, în special în comparație cu PHPUnit. În timp ce cadrul de batjocură al PHPUnit este puternic, Mockery oferă o limbă mai naturală, cu un set de matematici de tip Hamcrest. În acest articol, voi compara cele două cadre de batjocură și voi evidenția cele mai bune trăsături ale lui Mockery.

Dăruirea oferă un set de machete legate de batjocură, care sunt foarte asemănătoare cu un dicționar Hamcrest, oferind o modalitate foarte naturală de a exprima așteptările ridicate. Batjocura nu suprascrie sau nu contravine funcțiilor de batjocorire create de PHPUnit; de fapt, le puteți utiliza atât în ​​același timp (și chiar în aceeași metodă de testare).


Instalarea de batjocură

Există mai multe moduri de instalare a deranjamentului; aici sunt cele mai comune metode.

Utilizați compozitorul

Creați un fișier numit composer.json în dosarul rădăcină al proiectului dvs. și adăugați următorul cod în acel fișier:

"necesită": "Dăruire / batjocură": "> = 0.7.2"

Apoi, instalați pur și simplu Composer în dosarul rădăcină al proiectului utilizând următoarea comandă:

curl-uri http://getcomposer.org/installer | php

În cele din urmă, instalați toate dependențele cerute (inclusiv Dotări) cu această comandă:

php installer.phar install

Cu totul instalat, asigurați-vă că funcționează instalarea noastră de batjocură. Din motive de simplitate, presupun că ai un dosar, numit Test în directorul rădăcină al proiectului. Toate exemplele din acest tutorial se vor afla în acest dosar. Iată codul pe care l-am folosit pentru a mă asigura că Mockery lucrează cu proiectul meu:

// Numele fișierului: JustToCheckMockeryTest.php require_once '... /vendor/autoload.php'; clasa JustToCheckMockeryTest extinde PHPUnit_Framework_TestCase funcția protejată tearDown () \ Mockery :: close ();  funcția testMockeryWorks () $ mock = \ Mockery :: mock ("AClassToBeMocked"); $ Mock-> shouldReceive ( 'SomeMethod') -> o dată (); $ workerObject = nou AClassToWorkWith; $ WorkerObject-> doSomethingWit ($ mock);  clasa AClassToBeMocked  clasa AClassToWorkWith funcția doSomethingWit ($ anotherClass) return $ anotherClass-> someMethod (); 

Utilizatorii Linux: Utilizați pachetele Distro

Unele distribuții Linux facilitează instalarea Mockery, dar numai o mână oferă un pachet Mockery pentru sistemul lor. Următoarea listă este singura distracție pe care o cunosc:

  • Sabayon: equo install Mockery
  • Fedora / RHE: yum install Mockery

Utilizați PEAR

Fanii PEAR pot instala Mockery prin emiterea următoarelor comenzi:

sudo pear canal-descoperă pear.survivethedeepend.com sudo pear canal-descoperă hamcrest.googlecode.com/svn/pear sudo pear install --alldeps deepend / Dăruire

Instalarea de la sursă

Instalarea de la GitHub este pentru geeks real! Puteți să luați întotdeauna cea mai recentă versiune de Mockery prin magazia GitHub.

git clone git: //github.com/padraic/Mockery.git cd Mockery sudo pear canal-descoperi hamcrest.googlecode.com/svn/pear sudo pear install --alldeps package.xml

Crearea Primului nostru Obiect Mocked

Să batem câteva obiecte înainte să definim așteptările. Următorul cod va modifica exemplul anterior pentru a include exemple PHPUnit și Mockery:

// Numele fișierului: MockeryABetterWayOfMockingTest.php require_once '... /vendor/autoload.php'; class MockeryVersusPHPUnitGetMockTest extinde PHPUnit_Framework_TestCase function testCreateAMockedObject () // Cu PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); // Cu Mockery $ mockeryMock = \ Mockery :: mock ('AClassToBeMocked');  clasa AClassToBeMocked 

Dificultatea vă permite să definiți machete pentru clase care nu există.

Prima linie asigură că avem acces la Mockery. Apoi, vom crea o clasă de testare, numită MockeryVersusPHPUnitGetMockTest, care are o metodă, testCreateAMockedObject (). Clasa batjocorită, AClassToBeMocked, este complet gol în acest moment; de fapt, puteți elimina complet clasa fără a determina eșecul testului.

testCreateAMockedObject () metoda de testare definește două obiecte. Primul este un mock PHPUnit, iar al doilea este creat cu Mockery. Sintaxa de batjocură este:

$ mockedObject = \ Mockery :: mock ("SomeClassToBeMocked");

Atribuiți așteptări simple

Mocks sunt utilizate în mod obișnuit pentru a verifica comportamentul unui obiect (în primul rând metodele sale) prin specificarea a ceea ce este numit așteptări. Să formăm câteva așteptări simple.

Așteptați o metodă care trebuie chemată

Probabil cea mai comună așteptare este cea care așteaptă o metodă specifică. Cele mai multe cadre de batjocură vă permit să specificați numărul de apeluri pe care vă așteptați să primiți o metodă. Vom începe cu o simplă așteptare pentru un singur apel:

// Numele fișierului: MockeryABetterWayOfMockingTest.php require_once '... /vendor/autoload.php'; class MockeryVersusPHPUnitGetMockTest extinde PHPUnit_Framework_TestCase funcția protejată tearDown () \ Mockery :: close ();  funcția testExpectOnce () $ someObject = new SomeClass (); // Cu PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> se asteapta ($ this-> o dată ()) -> metoda ( 'SomeMethod'); // Exercițiu pentru PHPUnit $ someObject-> doSomething ($ phpunitMock); // Cu batjocura $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ MockeryMock-> shouldReceive ( 'SomeMethod') -> o dată (); // Exercițiu pentru batjocură $ someObject-> doSomething ($ mockeryMock);  clasa AClassToBeMocked function someMethod ()  clasa SomeClass function doSomething ($ anotherObject) $ anotherObject-> someMethod (); 

Acest cod configurează o așteptare atât pentru PHPUnit, cât și pentru Mockery. Să începem cu primul.

Unele distribuții Linux facilitează instalarea Mockery.

Noi folosim se așteaptă () metodă pentru a defini o așteptare de apel SomeMethod () o singura data. Dar pentru ca PHPUnit să funcționeze corect, noi trebuie sa defini o clasă numită AClassToBeMocked, și trebuie să aibă a SomeMethod () metodă.

Aceasta este o problemă. Dacă vă bateți o mulțime de obiecte și dezvoltați folosind principiile TDD pentru un design de sus în jos, nu doriți să creați toate clasele și metodele înainte de test. Testul dvs. ar trebui să eșueze din motivul potrivit, că nu a fost apelată metoda așteptată, în loc de o eroare PHP critică, fără nici o legătură cu implementarea reală. Continuați și încercați să eliminați SomeMethod () definiție de la AClassToBeMocked, și vezi ce se întâmplă.

Bataie, pe de altă parte, vă permite să definiți mocks pentru clasele care nu există.

Observați că exemplul de mai sus creează o falsă AnInexistentClass, care, după cum sugerează și numele său, nu există (nici nu SomeMethod () metodă).

La sfârșitul exemplului de mai sus, definim SomeClass să-și exercite codul. Inițializăm un obiect numit $ someObject în prima linie a metodei de testare, și vom exercita în mod eficient codul după definirea așteptărilor noastre.

Vă rugăm să rețineți: Batjocura evaluează așteptările sale închide() metodă. Din acest motiv, ar trebui să aveți întotdeauna a dărâma() pe testul pe care îl solicitați \ Batjocorirea :: close (). În caz contrar, Mockery dă false pozitive.

Asteptati mai mult de un apel

După cum am observat anterior, cele mai multe cadre de batjocură au capacitatea de a specifica așteptările pentru apelurile multiple de metode. PHPUnit utilizează $ This-> exact () construiți în acest scop. Următorul cod definește așteptările pentru a apela o metodă de mai multe ori:

funcția testExpectMultiple () $ someObject = new SomeClass (); // Cu PHPUnit de 2 ori $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> se asteapta ($ this-> exact (2)) -> metoda ( 'SomeMethod'); // Exercițiu pentru PHPUnit $ someObject-> doSomething ($ phpunitMock); $ SomeObject-> doSomething ($ phpunitMock); // Cu batjocură de 2 ori $ mockeryMock = \ Mockery :: mock ("AnInexistentClass"); $ MockeryMock-> shouldReceive ( 'SomeMethod') -> de două ori (); // Exercițiu pentru batjocură $ someObject-> doSomething ($ mockeryMock); $ SomeObject-> doSomething ($ mockeryMock); // Cu batjocură de 3 ori $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ MockeryMock-> shouldReceive ( 'SomeMethod') -> ori (3); // Exercițiu pentru batjocură $ someObject-> doSomething ($ mockeryMock); $ SomeObject-> doSomething ($ mockeryMock); $ SomeObject-> doSomething ($ mockeryMock); 

Dăruirea oferă două metode diferite pentru a vă satisface mai bine nevoile. Prima metodă, de două ori(), se așteaptă două apeluri metodice. Cealaltă metodă este ori (), care vă permite să specificați o sumă. Abordarea lui Mockery este mult mai curată și mai ușor de citit.


Valorile returnate

O altă utilizare obișnuită pentru machete este de a testa valoarea returnată a unei metode. Firește, ambele PHPUnit și Mockery au mijloacele de a verifica valorile returnate. Încă o dată, să începem cu ceva simplu.

Valori de revenire simple

Următorul cod conține atât cod PHPUnit, cât și cod Mockery. De asemenea, am actualizat SomeClass pentru a furniza o valoare de întoarcere verificabilă.

class MockeryVersusPHPUnitGetMockTest extinde PHPUnit_Framework_TestCase funcția protejată tearDown () \ Mockery :: close ();  // [...] // funcția testSimpleReturnValue () $ someObject = new SomeClass (); $ someValue = 'some value'; // Cu PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> se asteapta ($ this-> o dată ()) -> metoda ( 'SomeMethod') -> va ($ this-> returnValue ($ someValue)); // Așteptați valoarea returnată $ this-> assertEquals ($ someValue, $ someObject-> doSomething ($ phpunitMock)); // Cu batjocura $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ MockeryMock-> shouldReceive ( 'SomeMethod') -> o dată () -> andReturn ($ someValue); // Așteptați valoarea returnată $ this-> assertEquals ($ someValue, $ someObject-> doSomething ($ mockeryMock));  clasa AClassToBeMocked funcția someMethod ()  clasa SomeClass funcția doSomething ($ anotherObject) return $ anotherObject-> someMethod (); 

Atât API-ul PHPUnit, cât și Mockery's sunt simplu și ușor de utilizat, dar încă mai găsesc că batjocura este mai curată și mai ușor de citit.

Revenind diferite valori

Frecvenții testeri de unitate pot depune mărturie la complicații cu metode care returnează valori diferite. Din păcate, PHPUnit e limitat $ This-> la (index $) metoda este numai mod de a returna diferite valori de la aceeași metodă. Următorul cod demonstrează la() metodă:

funcția testDemonstratePHPUnitCallIndexing () $ someObject = new SomeClass (); $ firstValue = 'prima valoare'; $ secondValue = 'a doua valoare'; // Cu PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> se asteapta ($ this-> la (0)) -> metoda ( 'SomeMethod') -> va ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> se asteapta ($ this-> la (1)) -> metoda ( 'SomeMethod') -> va ($ this-> returnValue ($ secondValue)); // Așteptați valoarea returnată $ this-> assertEquals ($ firstValue, $ someObject-> doSomething ($ phpunitMock)); $ this-> assertEquals ($ secondValue, $ someObject-> doSomething ($ phpunitMock)); 

Acest cod definește două așteptări separate și face două apeluri diferite SomeMethod (); așa că trece acest test. Dar să introducem o răsucire și să adăugăm un dublu apel în clasa testată:

 // [...] // funcția testDemonstratePHPUnitCallIndexingOnTheSameClass () $ someObject = new SomeClass (); $ firstValue = 'prima valoare'; $ secondValue = 'a doua valoare'; // Cu PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> se asteapta ($ this-> la (0)) -> metoda ( 'SomeMethod') -> va ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> se asteapta ($ this-> la (1)) -> metoda ( 'SomeMethod') -> va ($ this-> returnValue ($ secondValue)); // Așteptați valoarea returnată $ this-> assertEquals ('prima valoare a doua valoare', $ someObject-> concatenate ($ phpunitMock));  clasa SomeClass funcția doSomething ($ anotherObject) return $ anotherObject-> someMethod ();  funcția concatenate ($ anotherObject) return $ anotherObject-> someMethod (). "$ anotherObject-> someMethod ();

Testul încă mai trece. PHPUnit se așteaptă la două apeluri către SomeMethod () care se întâmplă în interiorul clasei testate atunci când efectuează concatenarea prin înlănţui() metodă. Primul apel returnează prima valoare, iar al doilea apel returnează a doua valoare. Dar, iată captura: ce s-ar întâmpla dacă dublezi aserțiunea? Iată codul:

funcția testDemonstratePHPUnitCallIndexingOnTheSameClass () $ someObject = new SomeClass (); $ firstValue = 'prima valoare'; $ secondValue = 'a doua valoare'; // Cu PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> se asteapta ($ this-> la (0)) -> metoda ( 'SomeMethod') -> va ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> se asteapta ($ this-> la (1)) -> metoda ( 'SomeMethod') -> va ($ this-> returnValue ($ secondValue)); // Așteptați valoarea returnată $ this-> assertEquals ('prima valoare a doua valoare', $ someObject-> concatenate ($ phpunitMock)); $ this-> assertEquals ('prima valoare a doua valoare', $ someObject-> concatenate ($ phpunitMock)); 

Aceasta returnează următoarea eroare:

Nu s-a afirmat că două șiruri sunt egale. --- Așteptat +++ Actuală @@ @@ - "valoarea a doua a doua valoare" + "

PHPUnit continuă să numere între apeluri distincte la înlănţui(). Până când are loc al doilea apel în ultima afirmație, indicele $ este la valorile 2 și 3. Puteți face testul prin modificarea așteptărilor dvs. de a lua în considerare cei doi pași noi, cum ar fi:

funcția testDemonstratePHPUnitCallIndexingOnTheSameClass () $ someObject = new SomeClass (); $ firstValue = 'prima valoare'; $ secondValue = 'a doua valoare'; // Cu PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> se asteapta ($ this-> la (0)) -> metoda ( 'SomeMethod') -> va ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> se asteapta ($ this-> la (1)) -> metoda ( 'SomeMethod') -> va ($ this-> returnValue ($ secondValue)); $ PhpunitMock-> se asteapta ($ this-> la (2)) -> metoda ( 'SomeMethod') -> va ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> se asteapta ($ this-> la (3)) -> metoda ( 'SomeMethod') -> va ($ this-> returnValue ($ secondValue)); // Așteptați valoarea returnată $ this-> assertEquals ('prima valoare a doua valoare', $ someObject-> concatenate ($ phpunitMock)); $ this-> assertEquals ('prima valoare a doua valoare', $ someObject-> concatenate ($ phpunitMock)); 

Probabil că puteți trăi cu acest cod, dar Mockery face acest scenariu banal. Nu mă credeți? Aruncati o privire:

funcția testMultipleReturnValuesWithMockery () $ someObject = new SomeClass (); $ firstValue = 'prima valoare'; $ secondValue = 'a doua valoare'; // Cu batjocura $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ mockeryMock-> shouldReceive ('someMethod') -> șiReturn ($ firstValue, $ secondValue, $ firstValue, $ secondValue); // Așteptați valoarea returnată $ this-> assertEquals ('prima valoare a doua valoare', $ someObject-> concatenate ($ mockeryMock)); $ this-> assertEquals ('prima valoare a doua valoare', $ someObject-> concatenate ($ mockeryMock)); 

La fel ca PHPUnit, Mockery utilizează indexarea, dar nu trebuie să ne facem griji cu privire la indicii. În schimb, enumerăm doar toate valorile așteptate, iar Mockery le readuce în ordine.

În plus, PHPUnit se întoarce NUL pentru indecși nespecificați, dar Răscoala întoarce întotdeauna ultima valoare specificată. Asta eo atingere drăguță.

Încercați mai multe metode cu indexare

Să introducem oa doua metodă în codul nostru concatWithMinus () metodă:

clasa SomeClass funcția doSomething ($ anotherObject) return $ anotherObject-> someMethod ();  functie concatenate ($ anotherObject) return $ anotherObject-> someMethod () ". $ anotherObject-> someMethod (); functie concatWithMinus ($ anotherObject) return $ anotherObject-> anotherMethod (). -> altă metodă ();

Această metodă se comportă similar înlănţui(), dar concatenează valorile șir cu " - "spre deosebire de un singur spațiu.Deoarece aceste două metode îndeplinesc sarcini similare, este logic să le testați în cadrul aceleiași metode de testare pentru a evita testarea duplicat.

Așa cum sa demonstrat în codul de mai sus, a doua funcție folosește o metodă diferită numită anotherMethod (). Am făcut această schimbare pentru a ne forța să batem ambele metode în testele noastre. Clasa noastră moccătoare arată acum:

clasa AClassToBeMocked function someMethod ()  funcția anotherMethod () 

Testarea acestui lucru cu PHPUnit ar putea să arate după cum urmează:

funcția testPHPUnitIndexingOnMultipleMethods () $ someObject = new SomeClass (); $ firstValue = 'prima valoare'; $ secondValue = 'a doua valoare'; // Cu PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); // Primul și al doilea apel pe semeMethod: $ phpunitMock-> se așteaptă ($ this-> at (0)) -> method ('someMethod') -> va ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> se asteapta ($ this-> la (1)) -> metoda ( 'SomeMethod') -> va ($ this-> returnValue ($ secondValue)); // Așteptați valoarea returnată $ this-> assertEquals ('prima valoare a doua valoare', $ someObject-> concatenate ($ phpunitMock)); // Primul și al doilea apel pe celălalt Metodă: $ phpunitMock-> se așteaptă ($ this-> at (0)) -> method ('anotherMethod') -> will ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> se asteapta ($ this-> la (1)) -> metoda ( 'anotherMethod') -> va ($ this-> returnValue ($ secondValue)); // Așteptați valoarea returnată $ this-> assertEquals ('prima valoare - a doua valoare', $ someObject-> concatWithMinus ($ phpunitMock)); 

Logica este solidă. Definiți două așteptări diferite pentru fiecare metodă și specificați valoarea returnată. Aceasta funcționează numai cu PHPUnit 3.6 sau mai nou.

Vă rugăm să rețineți: PHPunit 3.5 și mai în vârstă a avut un bug care nu a resetat indicele pentru fiecare metodă, rezultând valori de întoarcere neașteptate pentru metodele deranjate.

Să ne uităm la același scenariu cu Mockery. Încă o dată, obținem un cod mult mai curat. Convinge-te singur:

funcția testMultipleReturnValuesForDifferentFunctionsWithMockery () $ someObject = new SomeClass (); $ firstValue = 'prima valoare'; $ secondValue = 'a doua valoare'; // Cu batjocura $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ mockeryMock-> shouldReceive ('someMethod') -> șiReturn ($ firstValue, $ secondValue); $ mockeryMock-> shouldReceive ('anotherMethod') -> șiReturn ($ firstValue, $ secondValue); // Așteptați valoarea returnată $ this-> assertEquals ('prima valoare a doua valoare', $ someObject-> concatenate ($ mockeryMock)); $ this-> assertEquals ('prima valoare - a doua valoare', $ someObject-> concatWithMinus ($ mockeryMock)); 

Valorile returnate pe baza parametrului dat

Sincer, acest lucru este ceva ce PHPUnit pur și simplu nu poate face. În momentul acestei scrieri, PHPUnit nu vă permite să returnați diferite valori de la aceeași funcție pe baza parametrului funcției. Prin urmare, următorul test nu reușește:

 // [...] // funcția testPHUnitCandDecideByParameter () $ someObject = new SomeClass (); // Cu PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> se asteapta ($ this-> orice ()) -> metoda ( 'getNumber') -> cu (2) -> va ($ this-> returnValue (2)); $ PhpunitMock-> se asteapta ($ this-> orice ()) -> metoda ( 'getNumber') -> cu (3) -> va ($ this-> returnValue (3)); $ this-> assertEquals (4, $ someObject-> doubleNumber ($ phpunitMock, 2)); $ this-> assertEquals (6, $ someObject-> doubleNumber ($ phpunitMock, 3));  clasa AClassToBeMocked // [...] // functie getNumber ($ number) return $ number;  clasa SomeClass // [...] // funcția doubleNumber ($ anotherObject, $ number) return $ anotherObject-> getNumber ($ number) * 2; 

Vă rugăm să ignorați faptul că nu există logică în acest exemplu; ar fi eșuat chiar dacă ar fi prezent. Acest cod, totuși, contribuie la ilustrarea ideii.

Acest test nu reușește deoarece PHPUnit nu poate face diferența între cele două așteptări din test. A doua așteptare, așteptând parametrul 3, pur și simplu suprascrie primul parametru așteptat 2. Dacă încercați să executați acest test, aveți următoarele erori:

Așteptarea nu a reușit pentru numele metodei este egală cu  când se invocă zero sau mai multe ori Parametrul 0 pentru invocarea AClassToBeMocked :: getNumber (2) nu se potrivește cu valoarea așteptată. Nu s-a afirmat că se așteaptă 2 meciuri 3.

Batjocura poate face acest lucru, iar codul de mai jos funcționează exact cum v-ați aștepta să funcționeze. Metoda returnează valori diferite pe baza parametrilor furnizați:

funcția testMockeryReturningDifferentValuesBasedOnParameter () $ someObject = new SomeClass (); // Mockery $ mockeryMock = \ Mockery :: mock ("AnInexistentClass"); $ MockeryMock-> shouldReceive ( 'getNumber') -> cu (2) -> andReturn (2); $ MockeryMock-> shouldReceive ( 'getNumber') -> (3) A -> andReturn (3); $ this-> assertEquals (4, $ someObject-> doubleNumber ($ mockeryMock, 2)); $ this-> assertEquals (6, $ someObject-> doubleNumber ($ mockeryMock, 3)); 

Momeală parțială

Uneori, doriți să machetați numai anumite metode pe obiect (spre deosebire de baterea unui obiect întreg). Următoarele Calculator clasa există deja; vrem să batem doar anumite metode:

Calculator de clasă add add ($ firstNo, $ secondNo) return $ firstNo + $ secondNo;  funcția scădea ($ firstNo, $ secondNo) return $ firstNo - $ secondNo;  multiplică funcția (valoare $, multiplicator $) $ newValue = 0; pentru ($ i = 0; $ i<$multiplier;$i++) $newValue = $this->adăugați ($ newValue, valoare $); returnează $ newValue; 

Acest Calculator clasa are trei metode: adăuga(), scădea(), și multiplica(). Multiplicarea utilizează o buclă pentru a efectua multiplicarea apelând adăuga() pentru o anumită cantitate de timp (de ex. 2 x 3 este într-adevăr 2 + 2 + 2).

Să presupunem că vrem să încercăm multiplica() în izolare totală; așa că ne vom bate joc adăuga() și verificați dacă există un comportament specific multiplica(). Iată câteva teste posibile:

funcția testPartialMocking () $ value = 3; $ multiplicator = 2; $ rezultat = 6; // PHPUnit $ phpMock = $ this-> getMock ('Calculator', array ('adaugă')); $ PhpMock-> se asteapta ($ this-> exact (2)) -> metoda ( 'add') -> va ($ this-> returnValue ($ rezultat)); $ this-> assertEquals ($ rezultat, $ phpMock-> multiplica (valoare $, multiplicator $)); // Mockery $ mockeryMock = \ Mockery :: mock (Calculator nou); $ MockeryMock-> shouldReceive ( 'add') -> andReturn ($ rezultat); $ this-> assertEquals ($ rezultat, $ mockeryMock-> multiplica (valoare $, multiplicator $)); // Parametri extinsi de verificare a testului mockery $ mockeryMock2 = \ Mockery :: mock (calculator nou); $ MockeryMock2-> shouldReceive ( 'add') -> cu (0,3) -> andReturn (3); $ MockeryMock2-> shouldReceive ( 'add') -> cu (3,3) -> andReturn (6); $ this-> assertEquals ($ rezultat, $ mockeryMock2-> multiplica (valoare $, multiplicator $)); 

Batjocura oferă ... o modalitate foarte naturală de a-și exprima așteptările înșelătoare.

Primul test PHPUnit este anemic; pur și simplu testează metoda adăuga() este sunat de două ori și returnează valoarea finală pentru fiecare apel. Se face treaba, dar este și un pic cam complicat. PHPUnit vă forțează să transmiteți lista metodelor pe care doriți să le mângâiți ca al doilea parametru $ This-> getMock (). În caz contrar, PHPUnit ar fi înșelat toate metodele, fiecare revenind NUL în mod implicit. Această listă trebuie sa să fie păstrate în concordanță cu așteptările pe care le definiți pe obiectul batjocorit.

De exemplu, dacă adaug oa doua așteptare $ phpMock„s scãdere () , PHPUnit l-ar ignora și va suna originalul scãdere () metodă. Asta este, dacă nu menționez explicit numele metodei (scãdere) în $ This-> getmock () afirmație.

Desigur, batjocura este diferită, permițându-vă să oferiți un obiect real \ Batjocorirea :: bate joc (), și creează automat o momeală parțială. Acest lucru este realizat prin implementarea unei soluții asemănătoare unui proxy pentru batjocură. Se utilizează toate așteptările pe care le definiți, dar Mockery revine la metoda originală dacă nu specificați o așteptare pentru acea metodă.

Vă rugăm să rețineți: Mockery abordare este foarte simplu, dar apelurile metodă internă nu trec prin obiect fals.

Acest exemplu este înșelător, dar ilustrează cum să nu utilizați Fraierile parțiale ale batjocoritoare. Da, batjocura creează o martoritate parțială dacă treci un obiect real, dar doar bănuiește numai apelurile externe. De exemplu, pe baza codului anterior, multiplica() metoda invocă realitatea adăuga() metodă. Continuați și încercați să schimbați ultima așteptare de la ... -> șiReturn (6) la ... -> șiReturn (7). În mod evident, testul ar trebui să nu reușească, dar nu este adevărat adăuga() execută în locul batjocoritorului adăuga() metodă.

Dar putem să evităm această problemă prin crearea de bătăi de genul:

// În loc de $ mockeryMock = \ Mockery :: mock (Calculator nou); // Creați momeala ca acest $ mockeryMock = \ Mockery :: mock ('Calculator [add]');

În timp ce este diferit din punct de vedere sintactic, conceptul este similar cu abordarea PHPUnit: trebuie să listați metodele deranjate în două locuri. Dar pentru orice alt test, puteți pur și simplu să treceți obiectul real, ceea ce este mult mai ușor - mai ales atunci când se ocupă de parametrii constructorului.


Confruntarea cu parametrii constructorului

Să adăugăm un constructor cu doi parametri la Calculator clasă. Codul revizuit:

Calculator de clasă public $ myNumbers = array (); funcția __construct ($ firstNo, $ secondNo) $ this-> myNumbers [] = $ firstNo; $ This-> myNumbers [] = $ secondNo;  // [...] //

Fiecare test din acest articol va eșua după adăugarea acestui constructor. Mai exact, testPartialMock () rezultatele testelor la următoarea eroare:

Lipsă argumentul 1 pentru Calculator :: __ construct (), numit în /usr/share/php/PHPUnit/Framework/MockObject/Generator.php pe linia 224 și definit

PHPUnit încearcă să bată jocul obișnuit apelând automat constructorul, așteptând ca parametrii să fie setați corect. Există două moduri în jurul acestei probleme: setați parametrii sau nu apelați constructorul.

// Specificați parametrii constructorului $ phpMock = $ this-> getMock ('Calculator', array ('add'), array (1,2)); // Nu apela constructorul original $ phpMock = $ this-> getMock ('Calculator', array ('add'), array (), ", false);

Bătaie de joc automagic lucrează în jurul acestei probleme. Este bine să nu specificați un parametru constructor; Batjocura pur si simplu nu va suna constructorul. Dar puteți specifica o listă de parametri constructori pentru a folosi Mockery. De exemplu:

funcția testMockeryConstructorParameters () $ result = 6; // Bateți // Nu apelați constructorul $ noConstrucCall = \ Mockery :: mock ('Calculator [add]'); $ NoConstrucCall-> shouldReceive ( 'add') -> andReturn ($ rezultat); // Folosiți parametrii constructorului $ withConstructParams = \ Mockery :: mock ('Calculator [add]', array (1,2)); $ WithConstructParams-> shouldReceive ( 'add') -> andReturn ($ rezultat); // Obiect utilizator real cu valori reale și bateți peste el $ realCalculator = Calculator nou (1,2); $ mockRealObj = \ Mockery :: mache ($ realCalculator); $ MockRealObj-> shouldReceive ( 'add') -> andReturn ($ rezultat); 

Considerații tehnice

Bătălia este o altă bibliotecă care integrează testele dvs. și poate doriți să țineți cont de implicațiile tehnice pe care le puteți avea.

  • Batjocura folosește o mulțime de memorie. Va trebui să măriți memoria maximă la 512 MB dacă doriți să executați mai multe teste (de exemplu peste 1000 de teste cu mai mult de 3000 de afirmații). Vedea php.ini documentație pentru detalii suplimentare.
  • Trebuie să organizați testele pentru a rula în procese separate, atunci când bateți metode statice și apeluri de metode statice.
  • Puteți încărca automat Mockery în fiecare test utilizând funcția de bootstrap a PHPUnit (utilă atunci când aveți multe teste și nu doriți să vă repetați).
  • Aveți posibilitatea să automatizați apelul \ Batjocorirea :: close () în fiecare test dărâma() prin editare phpunit.xml.

Concluzii finale

PHPUnit are cu siguranță problemele sale, mai ales atunci când vine vorba de funcționalitate și expresivitate. Batjocura poate imbunatati foarte mult experienta batjocorita facand testele dvs. usor de scris si de inteles - dar nu este perfect (nu exista asa ceva!).

Acest tutorial a evidențiat multe aspecte cheie ale batjocurii, dar, sincer, am zgâriat abia suprafața. Asigurați-vă că ați explorat depozitul Github al proiectului pentru a afla mai multe.

Vă mulțumim pentru lectură!

Cod