Spotgoed een betere manier

Mockery is een PHP-extensie die een superieure spotervaring biedt, vooral in vergelijking met PHPUnit. Hoewel PHPUnit's mocking-framework krachtig is, biedt Mockery een meer natuurlijke taal met een Hamcrest-achtige set matchers. In dit artikel vergelijk ik de twee spotmodellen en benadruk ik de beste kenmerken van Mockery.

Mockery biedt een set aan spotgerelateerde matchers die erg lijken op een Hamcrest-woordenboek, en die een heel natuurlijke manier bieden om bespotte verwachtingen uit te drukken. Spot heeft geen voorrang op of conflicteert met PHPUnit's ingebouwde spotfuncties; in feite kunt u ze allebei tegelijkertijd (en zelfs in dezelfde testmethode) gebruiken.


Mockery installeren

Er zijn meerdere manieren om Spot te installeren; hier zijn de meest gebruikelijke methoden.

Gebruik Composer

Maak een bestand met de naam composer.json in de hoofdmap van je project en voeg de volgende code toe aan dat bestand:

"require": "Mockery / Mockery": "> = 0.7.2"

Installeer vervolgens Composer eenvoudig in de hoofdmap van uw project met behulp van de volgende opdracht:

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

Installeer tot slot eventuele vereiste afhankelijkheden (inclusief Spot) met deze opdracht:

php composer.phar installeren

Als alles is geïnstalleerd, zorgen we ervoor dat onze Mockery-installatie werkt. Voor de eenvoud neem ik aan dat je een map hebt, genaamd Test in de hoofdmap van uw project. Alle voorbeelden in deze zelfstudie bevinden zich in deze map. Hier is de code die ik heb gebruikt om ervoor te zorgen dat Mockery met mijn project werkt:

// Bestandsnaam: JustToCheckMockeryTest.php require_once '... /vendor/autoload.php'; class JustToCheckMockeryTest breidt PHPUnit_Framework_TestCase uit protected function tearDown () \ Mockery :: close ();  function testMockeryWorks () $ mock = \ Mockery :: mock ('AClassToBeMocked'); $ Mock-> shouldReceive ( 'someMethod') -> één keer (); $ workerObject = new AClassToWorkWith; $ WorkerObject-> doSomethingWit ($ mock);  class AClassToBeMocked  class AClassToWorkWith function doSomethingWit ($ anotherClass) retourneer $ anotherClass-> someMethod (); 

Linux-gebruikers: gebruik de pakketten van uw Distro

Sommige Linux-distributies maken het gemakkelijk om Mockery te installeren, maar slechts een handjevol biedt een Mockery-pakket voor hun systeem. De volgende lijst zijn de enige distro's die ik ken:

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

Gebruik PEAR

PEAR-fans kunnen Mockery installeren door de volgende opdrachten te geven:

sudo pear channel-discover pear.survivethedeepend.com sudo pear channel-discover hamcrest.googlecode.com/svn/pear sudo pear install --alldeps deepend / Mockery

Installeren vanaf bron

Installeren van GitHub is voor de echte geeks! Je kunt altijd de nieuwste versie van Mockery pakken via zijn GitHub-repository.

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

Ons eerste bespot object maken

Laten we enkele objecten bespotten voordat we verwachtingen definiëren. De volgende code zal het vorige voorbeeld wijzigen om zowel PHPUnit- als Mockery-voorbeelden op te nemen:

// Bestandsnaam: MockeryABetterWayOfMockingTest.php require_once '... /vendor/autoload.php'; class MockeryVersusPHPUnitGetMockTest breidt PHPUnit_Framework_TestCase uit function testCreateAMockedObject () // With PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); // With Mockery $ mockeryMock = \ Mockery :: mock ('AClassToBeMocked');  class AClassToBeMocked 

Met spot kun je mocks definiëren voor klassen die niet bestaan.

De eerste regel zorgt ervoor dat we toegang hebben tot Mockery. Vervolgens maken we een testklasse, genaamd MockeryVersusPHPUnitGetMockTest, welke een methode heeft, testCreateAMockedObject (). De bespotste klas, AClassToBeMocked, is op dit moment helemaal leeg; in feite zou je de klasse volledig kunnen verwijderen zonder de test te laten mislukken.

De testCreateAMockedObject () testmethode definieert twee objecten. De eerste is een PHPUnit-mock en de tweede is gemaakt met Mockery. De syntaxis van Mockery is:

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

Wijs eenvoudige verwachtingen toe

Mocks worden vaak gebruikt om het gedrag van een object (voornamelijk de methoden) te verifiëren door op te geven wat er wordt genoemd verwachtingen. Laten we een paar eenvoudige verwachtingen instellen.

Verwacht een methode om te worden genoemd

Waarschijnlijk de meest voorkomende verwachting is er een die een specifieke methodeaanroep verwacht. In de meeste spotraamwerken kunt u het aantal oproepen opgeven dat u van een methode verwacht. We beginnen met een eenvoudige verwachting van één enkele oproep:

// Bestandsnaam: MockeryABetterWayOfMockingTest.php require_once '... /vendor/autoload.php'; class MockeryVersusPHPUnitGetMockTest breidt PHPUnit_Framework_TestCase uit protected function tearDown () \ Mockery :: close ();  function testExpectOnce () $ someObject = new SomeClass (); // Met PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> verwacht ($ this-> eenmaal ()) -> methode ( 'someMethod'); // Oefening voor PHPUnit $ someObject-> doSomething ($ phpunitMock); // With Mockery $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ MockeryMock-> shouldReceive ( 'someMethod') -> één keer (); // Oefening voor spot $ someObject-> doSomething ($ mockeryMock);  class AClassToBeMocked function someMethod ()  class SomeClass function doSomething ($ anotherObject) $ anotherObject-> someMethod (); 

Deze code configureert een verwachting voor zowel PHPUnit als Mockery. Laten we beginnen met de eerste.

Sommige Linux-distributies maken het eenvoudig om Mockery te installeren.

Wij gebruiken de verwacht () methode om een ​​verwachting om te bellen te definiëren someMethod () een keer. Maar om PHPUnit correct te laten werken, wij moet definieer een klasse genaamd AClassToBeMocked, en het moet een hebben someMethod () methode.

Dit is een probleem. Als je veel objecten bespot en ontwikkelt met TDD-principes voor een top-down ontwerp, zou je niet alle klassen en methoden voor je test willen maken. Uw test moet mislukken om de juiste reden, dat de verwachte methode niet is aangeroepen, in plaats van een kritieke PHP-fout zonder verband met de echte implementatie. Ga je gang en probeer het te verwijderen someMethod () definitie van AClassToBeMocked, en kijk wat er gebeurt.

Mockery daarentegen stelt je in staat om mocks te definiëren voor klassen die niet bestaan.

Merk op dat het bovenstaande voorbeeld een schijn creëert AnInexistentClass, wat zoals de naam aangeeft, niet bestaat (noch de zijne someMethod () methode).

Aan het einde van het bovenstaande voorbeeld definiëren we de SomeClass klas om onze code uit te oefenen. We initialiseren een object, genaamd $ someObject in de eerste regel van de testmethode, en we oefenen de code effectief uit na het definiëren van onze verwachtingen.

Houd er rekening mee dat: Spotterne evalueert de verwachtingen op zijn dichtbij() methode. Om deze reden zou u altijd een scheuren() methode op uw test die roept \ Mockery :: close (). Anders geeft Mockery valse positieven.

Verwacht meer dan één oproep

Zoals ik eerder heb opgemerkt, hebben de meeste spotmodellen de mogelijkheid om verwachtingen voor meerdere methodeaanroepen te specificeren. PHPUnit gebruikt de $ This-> precies () bouwen voor dit doel. De volgende code definieert de verwachtingen voor het meerdere keren aanroepen van een methode:

function testExpectMultiple () $ someObject = new SomeClass (); // Met PHPUnit 2 keer $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> verwacht ($ this-> precies (2)) -> methode ( 'someMethod'); // Oefening voor PHPUnit $ someObject-> doSomething ($ phpunitMock); $ SomeObject-> doSomething ($ phpunitMock); // With Mockery 2 times $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ MockeryMock-> shouldReceive ( 'someMethod') -> twee keer (); // Oefening voor spot $ someObject-> doSomething ($ mockeryMock); $ SomeObject-> doSomething ($ mockeryMock); // Met Mockery 3 keer $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ MockeryMock-> shouldReceive ( 'someMethod') -> keer (3); // Oefening voor spot $ someObject-> doSomething ($ mockeryMock); $ SomeObject-> doSomething ($ mockeryMock); $ SomeObject-> doSomething ($ mockeryMock); 

Mockery biedt twee verschillende methoden om beter aan uw behoeften te voldoen. De eerste methode, tweemaal(), verwacht twee methodeaanroepen. De andere methode is keer (), waarmee u een bedrag kunt opgeven. De aanpak van Mockery is veel schoner en gemakkelijker te lezen.


Waarden retourneren

Een ander veelgebruikt gebruik voor mocks is het testen van de retourwaarde van een methode. Uiteraard hebben zowel PHPUnit als Mockery de middelen om retourwaarden te verifiëren. Laten we opnieuw beginnen met iets eenvoudigs.

Simpele retourwaarden

De volgende code bevat zowel PHPUnit als Mockery-code. Ik heb ook bijgewerkt SomeClass om een ​​toetsbare retourwaarde te leveren.

class MockeryVersusPHPUnitGetMockTest breidt PHPUnit_Framework_TestCase uit protected function tearDown () \ Mockery :: close ();  // [...] // function test SimpleReturnValue () $ someObject = new SomeClass (); $ someValue = 'enige waarde'; // Met PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> verwacht ($ this-> eenmaal ()) -> methode ( 'someMethod') -> zal ($ this-> returnValue ($ someValue)); // Verwacht de geretourneerde waarde $ this-> assertEquals ($ someValue, $ someObject-> doSomething ($ phpunitMock)); // With Mockery $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ MockeryMock-> shouldReceive ( 'someMethod') -> één keer () -> andReturn ($ someValue); // Verwacht de geretourneerde waarde $ this-> assertEquals ($ someValue, $ someObject-> doSomething ($ mockeryMock));  class AClassToBeMocked function someMethod ()  class SomeClass function doSomething ($ anotherObject) retourneer $ anotherObject-> someMethod (); 

Zowel de API van PHPUnit als Mockery is eenvoudig en gebruiksvriendelijk, maar ik vind Mockery nog schoner en leesbaarder.

Verschillende waarden retourneren

Frequente eenheidstesters kunnen getuigen van complicaties met methoden die verschillende waarden retourneren. Helaas is PHPUnit beperkt $ This-> aan ($ index) methode is de enkel en alleen manier om verschillende waarden van dezelfde methode te retourneren. De volgende code demonstreert de op() methode:

function testDemonstratePHPUnitCallIndexing () $ someObject = new SomeClass (); $ firstValue = 'eerste waarde'; $ secondValue = 'tweede waarde'; // Met PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> verwacht ($ this-> aan (0)) -> methode ( 'someMethod') -> zal ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> verwacht ($ this-> aan (1)) -> methode ( 'someMethod') -> zal ($ this-> returnValue ($ secondValue)); // Verwacht de geretourneerde waarde $ this-> assertEquals ($ firstValue, $ someObject-> doSomething ($ phpunitMock)); $ this-> assertEquals ($ secondValue, $ someObject-> doSomething ($ phpunitMock)); 

Deze code definieert twee afzonderlijke verwachtingen en maakt twee verschillende aanroepen someMethod (); dus, deze test slaagt. Maar laten we een draai introduceren en een dubbel gesprek toevoegen in de geteste klasse:

 // [...] // functietestDemonstrerenPHPUnitCallIndexingOnTheSameClass () $ someObject = new SomeClass (); $ firstValue = 'eerste waarde'; $ secondValue = 'tweede waarde'; // Met PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> verwacht ($ this-> aan (0)) -> methode ( 'someMethod') -> zal ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> verwacht ($ this-> aan (1)) -> methode ( 'someMethod') -> zal ($ this-> returnValue ($ secondValue)); // Verwacht de geretourneerde waarde $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ phpunitMock));  class SomeClass function doSomething ($ anotherObject) retourneer $ anotherObject-> someMethod ();  functie concatenate ($ anotherObject) return $ anotherObject-> someMethod (). ". $ anotherObject-> someMethod ();

De test gaat nog steeds voorbij. PHPUnit verwacht twee oproepen naar someMethod () dat gebeurt binnen de geteste klasse bij het uitvoeren van de aaneenschakeling via de aaneenschakelen () methode. De eerste aanroep retourneert de eerste waarde en de tweede aanroep retourneert de tweede waarde. Maar hier is de vangst: wat zou er gebeuren als je de bewering verdubbelt? Hier is de code:

function testDemonstratePHPUnitCallIndexingOnTheSameClass () $ someObject = new SomeClass (); $ firstValue = 'eerste waarde'; $ secondValue = 'tweede waarde'; // Met PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> verwacht ($ this-> aan (0)) -> methode ( 'someMethod') -> zal ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> verwacht ($ this-> aan (1)) -> methode ( 'someMethod') -> zal ($ this-> returnValue ($ secondValue)); // Verwacht de geretourneerde waarde $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ phpunitMock)); $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ phpunitMock)); 

Het geeft de volgende foutmelding:

Mislukt dat twee snaren gelijk zijn. --- Verwacht +++ Actueel @@ @@ -'eerste waarde tweede waarde '+ "

PHPUnit blijft tellen tussen verschillende oproepen naar aaneenschakelen (). Tegen de tijd dat de tweede oproep in de laatste bewering plaatsvindt, $ index is tegen de waarden 2 en 3. U kunt de test laten slagen door uw verwachtingen aan te passen om de twee nieuwe stappen te overwegen, zoals deze:

function testDemonstratePHPUnitCallIndexingOnTheSameClass () $ someObject = new SomeClass (); $ firstValue = 'eerste waarde'; $ secondValue = 'tweede waarde'; // Met PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> verwacht ($ this-> aan (0)) -> methode ( 'someMethod') -> zal ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> verwacht ($ this-> aan (1)) -> methode ( 'someMethod') -> zal ($ this-> returnValue ($ secondValue)); $ PhpunitMock-> verwacht ($ this-> aan (2)) -> methode ( 'someMethod') -> zal ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> verwacht ($ this-> aan (3)) -> methode ( 'someMethod') -> zal ($ this-> returnValue ($ secondValue)); // Verwacht de geretourneerde waarde $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ phpunitMock)); $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ phpunitMock)); 

Je kunt waarschijnlijk met deze code leven, maar met Mockery is dit scenario triviaal. Geloof me niet? Kijk eens:

function testMultipleReturnValuesWithMockery () $ someObject = new SomeClass (); $ firstValue = 'eerste waarde'; $ secondValue = 'tweede waarde'; // With Mockery $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ mockeryMock-> shouldReceive ('someMethod') -> andReturn ($ firstValue, $ secondValue, $ firstValue, $ secondValue); // Verwacht de geretourneerde waarde $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ mockeryMock)); $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ mockeryMock)); 

Net als PHPUnit maakt Mockery gebruik van index tellen, maar we hoeven ons geen zorgen te maken over indices. In plaats daarvan geven we eenvoudig alle verwachte waarden weer en geeft Spotactie ze in volgorde weer.

Bovendien komt PHPUnit terug NUL voor niet-gespecificeerde indices, maar Spot geeft altijd de laatst opgegeven waarde. Dat is een leuke touch.

Probeer meerdere methoden met indexering

Laten we een tweede methode introduceren in onze code, de concatWithMinus () methode:

class SomeClass function doSomething ($ anotherObject) retourneer $ anotherObject-> someMethod ();  functie concatenate ($ anotherObject) return $ anotherObject-> someMethod (). ". $ anotherObject-> someMethod (); function concatWithMinus ($ anotherObject) return $ anotherObject-> anotherMethod (). '-'. $ anotherObject -> anotherMethod ();

Deze methode gedraagt ​​zich op dezelfde manier als aaneenschakelen (), maar het voegt de stringwaarden samen met " - "in tegenstelling tot een enkele spatie. Omdat deze twee methoden soortgelijke taken uitvoeren, is het zinvol om ze binnen dezelfde testmethode te testen om dubbele testen te voorkomen.

Zoals aangetoond in de bovenstaande code, gebruikt de tweede functie een andere spot-methode genaamd anotherMethod (). Ik heb deze wijziging aangebracht om ons te dwingen beide methoden in onze tests te bespotten. Onze mockable-klasse ziet er nu zo uit:

class AClassToBeMocked function someMethod ()  function anotherMethod () 

Het testen van dit met PHPUnit kan er als volgt uitzien:

function testPHPUnitIndexingOnMultipleMethods () $ someObject = new SomeClass (); $ firstValue = 'eerste waarde'; $ secondValue = 'tweede waarde'; // Met PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); // Eerste en tweede oproep op de semeMethod: $ phpunitMock-> verwacht ($ this-> op (0)) -> methode ('someMethod') -> will ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> verwacht ($ this-> aan (1)) -> methode ( 'someMethod') -> zal ($ this-> returnValue ($ secondValue)); // Verwacht de geretourneerde waarde $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ phpunitMock)); // Eerste en tweede aanroep op de andereMethode: $ phpunitMock-> verwacht ($ this-> op (0)) -> methode ('anotherMethod') -> will ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> verwacht ($ this-> aan (1)) -> methode ( 'anotherMethod') -> zal ($ this-> returnValue ($ secondValue)); // Verwacht de geretourneerde waarde $ this-> assertEquals ('first value - second value', $ someObject-> concatWithMinus ($ phpunitMock)); 

De logica is gezond. Definieer twee verschillende verwachtingen voor elke methode en specificeer de retourwaarde. Dit werkt alleen met PHPUnit 3.6 of nieuwer.

Houd er rekening mee dat: PHPunit 3.5 en ouder hadden een fout die de index voor elke methode niet opnieuw instelde, resulterend in onverwachte retourwaarden voor bespotte methoden.

Laten we hetzelfde scenario met Mockery bekijken. Nogmaals, we krijgen veel schonere code. Kijk zelf maar:

function testMultipleReturnValuesForDifferentFunctionsWithMockery () $ someObject = new SomeClass (); $ firstValue = 'eerste waarde'; $ secondValue = 'tweede waarde'; // With Mockery $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ mockeryMock-> shouldReceive ('someMethod') -> andReturn ($ firstValue, $ secondValue); $ mockeryMock-> shouldReceive ('anotherMethod') -> andReturn ($ firstValue, $ secondValue); // Verwacht de geretourneerde waarde $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ mockeryMock)); $ this-> assertEquals ('first value - second value', $ someObject-> concatWithMinus ($ mockeryMock)); 

Retourwaarden op basis van gegeven parameter

Eerlijk gezegd, dit is iets wat PHPU simpelweg niet kan. Op het moment dat u dit schrijft, staat PHPUnit niet toe dat u verschillende waarden van dezelfde functie retourneert op basis van de parameter van de functie. Daarom mislukt de volgende test:

 // [...] // functietestPHUnitCandDecideByParameter () $ someObject = new SomeClass (); // Met PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> verwacht ($ this-> elke ()) -> methode ( 'getNumber') -> met (2) -> zal ($ this-> returnValue (2)); $ PhpunitMock-> verwacht ($ this-> elke ()) -> methode ( 'getNumber') -> met (3) -> zal ($ this-> returnValue (3)); $ this-> assertEquals (4, $ someObject-> doubleNumber ($ phpunitMock, 2)); $ this-> assertEquals (6, $ someObject-> doubleNumber ($ phpunitMock, 3));  class AClassToBeMocked // [...] // functie getNumber ($ number) return $ number;  class SomeClass // [...] // function doubleNumber ($ anotherObject, $ number) return $ anotherObject-> getNumber ($ number) * 2; 

Negeer alstublieft het feit dat er geen logica is in dit voorbeeld; het zou falen, zelfs als het aanwezig was. Deze code helpt echter wel om het idee te illustreren.

Deze test mislukt omdat PHPUnit geen onderscheid kan maken tussen de twee verwachtingen in de test. De tweede verwachting, verwacht parameter 3, overschrijft eenvoudigweg de eerste verwachtende parameter 2. Als u probeert deze test uit te voeren, krijgt u de volgende foutmelding:

Verwachting mislukt voor methode naam is gelijk aan  bij nul of meerdere keren aangeroepen Parameter 0 voor aanroeping AClassToBeMocked :: getNumber (2) komt niet overeen met verwachte waarde. Mislukt bewerend dat 2 overeenkomsten verwacht 3 zijn.

Spotting kan dit, en de onderstaande code werkt precies zoals je zou verwachten dat het werkt. De methode retourneert verschillende waarden op basis van de opgegeven parameters:

function testMockeryReturningDifferentValuesBasedOnParameter () $ someObject = new SomeClass (); // Mockery $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ MockeryMock-> shouldReceive ( 'getNumber') -> met (2) -> andReturn (2); $ MockeryMock-> shouldReceive ( 'getNumber') -> met (3) -> andReturn (3); $ this-> assertEquals (4, $ someObject-> doubleNumber ($ mockeryMock, 2)); $ this-> assertEquals (6, $ someObject-> doubleNumber ($ mockeryMock, 3)); 

Gedeeltelijke mops

Soms wil je alleen bepaalde methoden van je object bespotten (in tegenstelling tot het bespotten van een volledig object). Het volgende Rekenmachine klasse bestaat al; we willen alleen bepaalde methoden bespotten:

class Calculator functie add ($ firstNo, $ secondNo) return $ firstNo + $ secondNo;  functie aftrekken ($ firstNo, $ secondNo) return $ firstNo - $ secondNo;  functie vermenigvuldigen ($ waarde, $ vermenigvuldiger) $ newValue = 0; voor ($ i = 0; $ i<$multiplier;$i++) $newValue = $this->add ($ newValue, $ value); return $ newValue; 

Deze Rekenmachine klasse heeft drie methoden: toevoegen(), aftrekken(), en vermenigvuldigen(). Multiply gebruikt een lus om de vermenigvuldiging uit te voeren door de toevoegen() gedurende een gespecificeerd aantal keren (bijv. 2 x 3 is echt 2 + 2 + 2).

Laten we aannemen dat we willen testen vermenigvuldigen() in totale isolatie; dus, we zullen bespotten toevoegen() en controleer op specifiek gedrag op vermenigvuldigen(). Hier zijn enkele mogelijke tests:

function testPartialMocking () $ value = 3; $ vermenigvuldiger = 2; $ resultaat = 6; // PHPUnit $ phpMock = $ this-> getMock ('Calculator', array ('add')); $ PhpMock-> verwacht ($ this-> precies (2)) -> methode ( 'add') -> zal ($ this-> returnValue ($ result)); $ this-> assertEquals ($ result, $ phpMock-> multiply ($ value, $ multiplier)); // Mockery $ mockeryMock = \ Mockery :: mock (nieuwe Rekenmachine); $ MockeryMock-> shouldReceive ( 'add') -> andReturn ($ result); $ this-> assertEquals ($ result, $ mockeryMock-> multiply ($ value, $ multiplier)); // Spot uitgebreide testcontroleparameters $ mockeryMock2 = \ Mockery :: mock (nieuwe Calculator); $ MockeryMock2-> shouldReceive ( 'add') -> met (0,3) -> andReturn (3); $ MockeryMock2-> shouldReceive ( 'add') -> met (3,3) -> andReturn (6); $ this-> assertEquals ($ result, $ mockeryMock2-> multiply ($ value, $ multiplier)); 

Spotternij biedt ... een heel natuurlijke manier om spotachtige verwachtingen tot uitdrukking te brengen.

De eerste PHPUnit-test is anemisch; het test gewoon die methode toevoegen() wordt tweemaal aangeroepen en retourneert de laatste waarde voor elke oproep. Het klaart de klus, maar het is ook een beetje ingewikkeld. PHPUnit dwingt je om de lijst met methoden die je wilt bespotten als tweede parameter door te geven $ This-> getMock (). Anders zou PHPUnit alle methoden bespotten, waarbij elke methode terugkeert NUL standaard. Deze lijst moet worden gehouden in overeenstemming met de verwachtingen die je definieert op je bespotte object.

Als ik bijvoorbeeld een tweede verwachting toevoeg $ phpMock's aftrekken () methode, zou PHPUnit het negeren en het origineel bellen aftrekken () methode. Dat wil zeggen, tenzij ik de naam van de methode expliciet geef (aftrekken) in de $ This-> getmock () uitspraak.

Natuurlijk is Mockery anders door je toe te staan ​​een echt object aan te bieden \ Mockery :: mock (), en het maakt automatisch een gedeeltelijke mock. Dit wordt bereikt door een proxy-achtige oplossing voor spotwerking te implementeren. Alle verwachtingen die je definieert worden gebruikt, maar Spot komt terug op de originele methode als je geen verwachting voor die methode opgeeft.

Houd er rekening mee dat: De aanpak van Mockery is heel eenvoudig, maar interne methodeaanroepen gaan niet door het bespotte object.

Dit voorbeeld is misleidend, maar het illustreert dit hoe niet te gebruiken De gedeeltelijke spot van spot. Ja, schijn maakt een gedeeltelijke minachting als je een echt object passeert, maar het maakt alleen maar spot alleen externe oproepen. Op basis van de vorige code, bijvoorbeeld vermenigvuldigen() methode noemt het echte toevoegen() methode. Ga je gang en probeer de laatste verwachting te veranderen ... -> andReturn (6) naar ... -> andReturn (7). De test moet natuurlijk mislukken, maar dat komt niet omdat het echt is toevoegen() wordt uitgevoerd in plaats van de spot toevoegen() methode.

Maar we kunnen dit probleem omzeilen door moppen als dit te maken:

// In plaats van $ mockeryMock = \ Mockery :: mock (nieuwe Calculator); // Maak de mock zoals deze $ mockeryMock = \ Mockery :: mock ('Calculator [add]');

Hoewel het concept syntactisch anders is, lijkt het op de aanpak van PHPUnit: je moet de bespotte methoden op twee plaatsen vermelden. Maar voor elke andere test, kunt u gewoon het echte object passeren, wat veel eenvoudiger is - vooral als het gaat om constructorfarameters.


Omgaan met constructorparameters

Laten we een constructor met twee parameters toevoegen aan de Rekenmachine klasse. De herziene code:

class Calculator public $ myNumbers = array (); function __construct ($ firstNo, $ secondNo) $ this-> myNumbers [] = $ firstNo; $ This-> myNumbers [] = $ secondNo;  // [...] //

Elke test in dit artikel zal mislukken na het toevoegen van deze constructor. Meer bepaald, de testPartialMock () test de resultaten in de volgende fout:

Ontbrekend argument 1 voor Rekenmachine :: __ construct (), genoemd in /usr/share/php/PHPUnit/Framework/MockObject/Generator.php op regel 224 en gedefinieerd

PHPUnit probeert het echte object te bespotten door de constructor automatisch te bellen, in de verwachting dat de parameters correct zijn ingesteld. Er zijn twee manieren om dit probleem op te lossen: stel de parameters in of bel de constructor niet.

// Specificeer Constructorparameters $ phpMock = $ this-> getMock ('Calculator', array ('add'), array (1,2)); // Bel niet de oorspronkelijke constructor $ phpMock = $ this-> getMock ('Calculator', array ('add'), array (), ", false);

Spot automagisch werkt rond dit probleem. Het is prima om geen constructorfactor op te geven; Spot zal de constructeur eenvoudigweg niet bellen. Maar u kunt een lijst met constructorfarameters opgeven voor Mockery om te gebruiken. Bijvoorbeeld:

function testMockeryConstructorParameters () $ result = 6; // Spotting // Noem geen constructor $ noConstrucCall = \ Mockery :: mock ('Calculator [add]'); $ NoConstrucCall-> shouldReceive ( 'add') -> andReturn ($ result); // Gebruik constructorparameters $ withConstructParams = \ Mockery :: mock ('Calculator [add]', array (1,2)); $ WithConstructParams-> shouldReceive ( 'add') -> andReturn ($ result); // Gebruiker echt object met echte waarden en bespotten $ realCalculator = nieuwe Calculator (1,2); $ mockRealObj = \ Mockery :: mock ($ realCalculator); $ MockRealObj-> shouldReceive ( 'add') -> andReturn ($ result); 

Technische overwegingen

Spot is een andere bibliotheek die je tests integreert, en je zou misschien willen overwegen welke technische implicaties dit kan hebben.

  • Spotgoed gebruikt veel geheugen. Je zult het maximale geheugen moeten verhogen naar 512 MB als je veel tests wilt uitvoeren (zeg meer dan 1000 tests met meer dan 3000 assertions). Zien php.ini documentatie voor verdere details.
  • U moet uw tests zodanig organiseren dat ze in afzonderlijke processen worden uitgevoerd, bij het bespotten van statische methoden en oproepen met statische methoden.
  • Je kunt Spot automatisch in elke test laden door de bootstrap-functionaliteit van PHPUnit te gebruiken (handig als je veel tests hebt en je jezelf niet wilt herhalen).
  • U kunt de oproep automatiseren \ Mockery :: close () in elke test scheuren() door te bewerken phpunit.xml.

Eindconclusies

PHPUnit heeft zeker zijn problemen, vooral als het gaat om functionaliteit en expressiviteit. Spotternij kan je beleving enorm verbeteren door je tests eenvoudig te schrijven en te begrijpen, maar het is niet perfect (zoiets bestaat niet!).

Deze tutorial heeft veel belangrijke aspecten van spot belicht, maar eerlijk gezegd hebben we nauwelijks de oppervlakte bekrast. Zorg ervoor dat u de Github-opslagplaats van het project verkent voor meer informatie.

Bedankt voor het lezen!