Het is een ongelukkige waarheid dat, hoewel het basisprincipe achter testen vrij eenvoudig is, het volledig moeilijk maken van dit proces in uw dagelijkse codeerworkflow moeilijker is dan u zou hopen. Alleen al het verschillende jargon kan overweldigend zijn! Gelukkig heeft een verscheidenheid aan hulpmiddelen uw rug en helpt het proces zo eenvoudig mogelijk te maken. Mockery, het belangrijkste mock-objectraamwerk voor PHP, is zo'n tool!
In dit artikel zullen we ingaan op wat spot is, waarom het nuttig is en hoe je Spot in je testworkflow kunt integreren.
Een nep-object is niets meer dan een beetje testjargon dat verwijst naar het simuleren van het gedrag van echte objecten. In eenvoudiger bewoordingen, zult u tijdens het testen vaak een bepaalde methode niet willen uitvoeren. In plaats daarvan moet je er gewoon voor zorgen dat het in feite wordt genoemd.
Misschien is een voorbeeld in orde. Stel je voor dat je code een methode activeert die een beetje gegevens naar een bestand zal loggen. Bij het testen van deze logica, wil je zeker niet het bestandssysteem fysiek aanraken. Dit heeft het potentieel om de snelheid van uw tests drastisch te verlagen. In deze situaties kun je het beste je bestandssysteemklasse bespotten en in plaats van het bestand handmatig te lezen om te bewijzen dat het is bijgewerkt, zorg je er alleen voor dat de toepasselijke methode in de klasse in feite wordt aangeroepen. Dit is spot! Er is niets meer aan de hand dan dat; het gedrag van objecten simuleren.
Vergeet niet: jargon is slechts een jargon. Sta nooit toe dat een verwarrend stukje terminologie u ervan weerhoudt een nieuwe vaardigheid te leren.
Vooral als uw ontwikkelingsproces verloopt - inclusief het omarmen van het beginsel van één verantwoordelijkheid en het gebruik van afhankelijkheidsinjectie - zal vertrouwdheid met spotacties snel essentieel worden.
Mocks vs. Stubs: De kans is groot dat je de termen vaak zult horen, bespotten en stomp, rond elkaar geworpen. In feite dienen de twee verschillende doeleinden. De eerste verwijst naar het proces van het definiëren van verwachtingen en het zorgen voor gewenst gedrag. Met andere woorden, een mock kan mogelijk leiden tot een mislukte test. Een stomp is daarentegen eenvoudig een dummy set met gegevens die kan worden doorgegeven om aan bepaalde criteria te voldoen.
De defacto-testbibliotheek voor PHP, PHPUnit, wordt geleverd met een eigen API voor het bespotten van objecten; Helaas kan het echter lastig zijn om ermee te werken. Zoals je ongetwijfeld weet, des te moeilijker het testen is, des te waarschijnlijker is het dat de ontwikkelaar het eenvoudig (en helaas) niet zal doen.
Gelukkig zijn er verschillende oplossingen van derden beschikbaar via Packagist (de pakketrepository van Composer), die een grotere leesbaarheid mogelijk maakt en, nog belangrijker,, writeability. Een van deze oplossingen - en het meest opvallende van de set - is Mockery, een kader-agnostisch mock-objectraamwerk.
Ontworpen als een drop-in alternatief voor diegenen die overweldigd zijn door de spottende breedsprakigheid van PHPUnit, is Mockery een eenvoudig maar krachtig hulpmiddel. Zoals je ongetwijfeld zult vinden, is het in feite de industrienorm voor moderne PHP-ontwikkeling.
Zoals de meeste PHP-tools tegenwoordig, is de aanbevolen methode om Mockery te installeren via Composer (hoewel het ook beschikbaar is via Pear).
Wacht, wat is dit Composer-ding? Het is het favoriete hulpmiddel van de PHP-community voor afhankelijkheidsbeheer. Het biedt een eenvoudige manier om de afhankelijkheden van een project te declareren en ze in te voeren met een enkele opdracht. Als moderne PHP-ontwikkelaar is het van groot belang dat je een basiskennis hebt van wat Composer is en hoe je het kunt gebruiken.
Als u meewerkt, voegt u voor het leren een nieuw toe composer.json
bestand naar een leeg project en toevoegen:
"require-dev": "spot / spot": "dev-master"
Dit bit van JSON geeft aan dat voor ontwikkeling uw toepassing de Mockery-bibliotheek nodig heeft. Vanaf de opdrachtregel, a composer install --dev
zal het pakket binnenhalen.
$ composer install --dev Composer-repositories laden met pakketinformatie afhankelijkheden installeren (inclusief require-dev) - Installeren van spot / spot (dev-master 5a71299) Klonen 5a712994e1e3ee604b0d355d1af342172c6f475f Lock-bestand schrijven Autoload-bestanden genereren
Als extra bonus wordt Composer gratis geleverd met een eigen autoloader! Specificeer een classmap met mappen en
componist dump-autoload
, of volg de PSR-0-standaard en pas uw directorystructuur aan. Raadpleeg Nettuts + voor meer informatie. Als je nog steeds handmatig talloze bestanden in elk PHP-bestand nodig hebt, dan zou je het misschien verkeerd kunnen doen.
Voordat we een oplossing kunnen implementeren, is het het beste om eerst het probleem te bekijken. Stel je voor dat je een systeem moet implementeren voor het verwerken van het proces van het genereren van inhoud en het schrijven ervan naar een bestand. Misschien verzamelt de generator verschillende gegevens, hetzij van lokale bestandsstubs of een webservice, en vervolgens worden die gegevens naar het bestandssysteem geschreven.
Als u het beginsel van één enkele verantwoordelijkheid volgt - wat dicteert dat elke klasse verantwoordelijk moet zijn voor precies één ding - dan is het logisch dat we deze logica in twee klassen splitsen: een voor het genereren van de benodigde inhoud en een andere voor het fysiek schrijven van de gegevens in een bestand. EEN Generator
en het dossier
klasse, moet het lukken.
Tip: Waarom niet gebruiken
file_put_contents
rechtstreeks van deGenerator
klasse? Nou, vraag jezelf af: "Hoe kan ik dit testen?"Er zijn technieken, zoals monkey-patching, waarmee je dit soort dingen kunt overbelasten, maar in de praktijk is het beter om dergelijke functionaliteit in plaats daarvan in te pakken, zodat het gemakkelijk kan worden bespot met hulpmiddelen, zoals spot!
Hier is een basisstructuur (met een gezonde dosis pseudo-code) voor onze Generator
klasse.
bestand = $ bestand; beschermde functie getContent () // vereenvoudigd voor demo-retour 'foo bar'; public function fire () $ content = $ this-> getContent (); $ this-> file-> put ('foo.txt', $ content);
Deze code maakt gebruik van wat we injectie van afhankelijkheid noemen. Nogmaals, dit is eenvoudigweg een ontwikkelaarjargon voor het injecteren van de afhankelijkheden van een klasse via de constructormethode, in plaats van het hardcoderen ervan.
Waarom is dit nuttig? Omdat we anders niet zouden kunnen spotten met de het dossier
klasse! Natuurlijk kunnen we de spot drijven met het dossier
klasse, maar als de instantiatie ervan hard gecodeerd is in de klasse die we testen, is er geen gemakkelijke manier om die instantie te vervangen door de bespotte versie.
publieke functie __construct () // anti-pattern $ this-> file = new File;
De beste manier om testbare applicaties te bouwen is om elke nieuwe methodeaanroep te benaderen met de vraag: "Hoe kan ik dit testen?"Hoewel er trucjes zijn om deze harde codering te omzeilen, wordt dit algemeen beschouwd als een slechte gewoonte, maar injecteer altijd de afhankelijkheden van een klasse via de constructor of via de injectie van de setter.
Setterinjectie is min of meer identiek aan constructorinjectie. Het principe is precies hetzelfde; het enige verschil is dat, in plaats daarvan de afhankelijkheden van de klasse door de constructormethode te injecteren, ze in plaats daarvan zo worden gedaan via een settermethode, zoals:
openbare functie setFile (bestand $ bestand) $ this-> bestand = $ bestand;
Een veelgehoorde kritiek op afhankelijkheidsinjectie is dat het extra complexiteit in een toepassing introduceert, allemaal om het meer toetsbaar te maken. Hoewel het complexiteitsargument in de ogen van deze auteur betwistbaar is, kunt u afhankelijkheidsinjectie toestaan, terwijl u nog steeds fallback-standaardwaarden opgeeft. Hier is een voorbeeld:
class Generator public function __construct (File $ file = null) $ this-> file = $ file?: new File;
Nu, als een instantie van het dossier
wordt doorgegeven aan de constructor, dat object zal in de klas worden gebruikt. Aan de andere kant, als er niets wordt doorgegeven, de Generator
zullen terugvallen om de toepasselijke klasse handmatig te instantiëren. Dit maakt variaties mogelijk als:
# Klasse maakt een nieuwe File Generator; # Injecteer een nieuwe Generator (nieuw bestand); # Injecteer een mock File om een nieuwe Generator te testen ($ mockedFile);
Verdergaand op, voor de doeleinden van deze tutorial, de het dossier
klasse zal niets meer zijn dan een eenvoudige verpakking rond PHP's file_put_contents
functie.
Eerder eenvoudig, toch? Laten we een test schrijven om uit eerste hand te zien wat het probleem is.
brand();Houd er rekening mee dat deze voorbeelden aannemen dat de benodigde klassen automatisch worden geladen met Composer. Jouw
composer.json
bestand accepteert optioneel eenautoload
object, waar u kunt opgeven welke mappen of klassen automatisch moeten worden bijgewerkt. Niet meer rommeligvereisen
statements!Als je werkt, rennen
PHPUnit
zal terugkeren:OK (1 test, 0 beweringen)Het is groen; dat betekent dat we door kunnen gaan naar de volgende taak, toch? Nou, niet precies. Hoewel het waar is dat de code inderdaad werkt, wordt elke keer dat deze test wordt uitgevoerd, een
Hoewel de tests slagen, raken ze het bestandssysteem onjuist.foo.txt
bestand wordt aangemaakt op het bestandssysteem. En als je nog tientallen tests hebt geschreven? Zoals je je heel snel kunt voorstellen, zal de snelheid van uitvoering van je test stotteren.Nog steeds niet overtuigd? Als de testsnelheid niet slechter is, overweeg dan het gezond verstand. Denk erover na: we testen de
Generator
klasse; waarom hebben we enig belang bij het uitvoeren van code van dehet dossier
klasse? Het zou zijn eigen tests moeten hebben! Waarom zouden we in vredesnaam verdubbelen?
De oplossing
Hopelijk was het vorige deel de perfecte illustratie voor waarom spotten essentieel is. Zoals eerder opgemerkt, hoewel we de API van PHPUnit kunnen gebruiken om onze spotvereisten te vervullen, is het niet al te plezierig om ermee te werken. Om deze waarheid te illustreren, hier is een voorbeeld om te beweren dat een bespot object een methode zou moeten ontvangen,
getName
En terugkomenJohn Doe
.public function testNativeMocks () $ mock = $ this-> getMock ('SomeClass'); $ mock-> verwacht ($ this-> once ()) -> methode ('getName') -> will ($ this-> returnValue ('John Doe'));Terwijl het de klus wordt geklaard - bewerend dat a
getName
methode wordt eenmaal genoemd en retourneert John Doe - PHPUnit's implementatie is verwarrend en uitgebreid. Met Mockery kunnen we de leesbaarheid drastisch verbeteren.public function testMockery () $ mock = Mockery :: mock ('SomeClass'); $ mock-> shouldReceive ('getName') -> once () -> andReturn ('John Doe');Merk op hoe het laatste voorbeeld beter leest (en spreekt).
Doorgaan met het voorbeeld van de vorige "Dilemma sectie, deze keer, binnen de
GeneratorTest
klasse, laten we in plaats daarvan mocken - of het gedrag van - simulerenhet dossier
klas met Mockery. Hier is de bijgewerkte code:shouldReceive ('put') -> with ('foo.txt', 'foo bar') -> once (); $ generator = nieuwe Generator ($ mockedFile); $ Generator-> brand ();Verward door de
Mockery :: close ()
verwijzing binnen descheuren
methode? Deze statische aanroep ruimt de Mockery-container op die wordt gebruikt door de huidige test en voert verificatietaken uit die nodig zijn voor uw verwachtingen.Een klasse kan worden bespot met behulp van de leesbare
Mockery :: mock ()
methode. Vervolgens moet u meestal opgeven welke methoden voor dit mock-object u verwacht te worden aangeroepen, samen met eventuele toepasselijke argumenten. Dit kan worden bereikt via deshouldReceive (WERKWIJZE)
enmet (ARG)
methoden.In dit geval, wanneer we bellen
$ Generate-> brand ()
, we beweren dat het deleggen
methode op dehet dossier
bijvoorbeeld, en stuur het het pad,foo.txt
, en de gegevens,foo bar
.// bibliotheken / Generator.php public function fire () $ content = $ this-> getContent (); $ this-> file-> put ('foo.txt', $ content);Omdat we afhankelijkheidsinjectie gebruiken, is het nu een makkie om de spot met de injectie te injecteren
het dossier
voorwerp.$ generator = nieuwe Generator ($ mockedFile);Als we de tests opnieuw uitvoeren, worden ze nog steeds groen weergegeven, maar de
het dossier
klasse - en bijgevolg het bestandssysteem - zal nooit worden aangeraakt! Nogmaals, het is niet nodig om aan te rakenhet dossier
. Het zou zijn eigen tests moeten hebben! Bespotten voor de overwinning!Eenvoudige onechte objecten
Mock-objecten hoeven niet altijd naar een klasse te verwijzen. Als u alleen een eenvoudig object nodig hebt, misschien voor een gebruiker, geeft u mogelijk een array door aan de
bespotten
methode - waarbij, voor elk item, de sleutel en de waarde overeenkomen met respectievelijk de methode- naam en retourwaarde.public function testSimpleMocks () $ user = Mockery :: mock (['getFullName' => 'Jeffrey Way']); $ Gebruiksvriendelijkheid> getFullName (); // Jeffrey WayRetourwaarden van bespotte methoden
Er zullen ongetwijfeld momenten zijn waarop een methode met een bespotte klasse een waarde moet teruggeven. Doorgaan met ons Generator / Bestand voorbeeld, wat als we moeten zorgen dat, als het bestand al bestaat, het niet overschreven moet worden? Hoe kunnen we dat bereiken?
De sleutel is om de
En terugkomen()
methode op je bespotte object om anders te simuleren staten. Hier is een bijgewerkt voorbeeld:public function testDoesNotOverwriteFile () $ mockedFile = Spot: :: mock ('Bestand'); $ mockedFile-> shouldReceive ('exists') -> once () -> andReturn (true); $ mockedFile-> shouldReceive ('put') -> never (); $ generator = nieuwe Generator ($ mockedFile); $ Generator-> brand ();Deze bijgewerkte code stelt nu dat een
bestaat
methode moet worden geactiveerd op de spothet dossier
klasse, en het moet, voor de doeleinden van het pad van deze test, terugkerenwaar
, signalering dat het bestand al bestaat en niet mag worden overschreven. Vervolgens zorgen we ervoor dat, in situaties zoals deze, deleggen
methode op dehet dossier
klasse wordt nooit geactiveerd. Met Mockery is dit gemakkelijk, dankzij denooit()
verwachting.$ mockedFile-> shouldReceive ('put') -> never ();Als we de tests opnieuw uitvoeren, wordt er een fout geretourneerd:
Methode bestaat () uit Bestand moet precies 1 keer worden aangeroepen, maar 0 keer worden genoemd.Aha; dus de test verwachtte dat
$ This-> file-> bestaat ()
moet worden gebeld, maar dat is nooit gebeurd. Als zodanig is mislukt. Laten we het oplossen!bestand = $ bestand; beschermde functie getContent () // vereenvoudigd voor demo-retour 'foo bar'; public function fire () $ content = $ this-> getContent (); $ file = 'foo.txt'; if (! $ this-> file-> exists ($ file)) $ this-> file-> put ($ file, $ content);Dat is alles wat er is! Niet alleen hebben we een TDD-cyclus (testgestuurde ontwikkeling) gevolgd, maar de tests zijn weer groen!
Het is belangrijk om te onthouden dat deze manier van testen alleen effectief is als je ook de afhankelijkheden van je klas test! Anders zal de code doorbreken, ook al kunnen de tests groen zijn, voor productie. Onze demo heeft tot nu toe alleen gezorgd voor dat
Generator
werkt zoals verwacht. Vergeet niet te testenhet dossier
ook!
verwachtingen
Laten we een beetje dieper graven in de verwachtingen van Mockery. Je bent er al bekend mee
shouldReceive
. Wees hier echter voorzichtig mee; de naam is een beetje misleidend. Als het op zichzelf staat, hoeft de methode niet te worden geactiveerd; de standaardwaarde is nul of vaker (zeroOrMoreTimes ()
). Als u wilt beweren dat u de methode één keer of mogelijk vaker wilt laten kiezen, zijn er een handvol opties beschikbaar:$ Mock-> shouldReceive ( 'methode') -> één keer (); $ Mock-> shouldReceive ( 'methode') -> keer (1); $ Mock-> shouldReceive ( 'methode') -> Minstens () -> keer (1);Er zullen momenten zijn waarop extra beperkingen nodig zijn. Zoals eerder aangetoond, kan dit met name handig zijn wanneer u ervoor moet zorgen dat een bepaalde methode wordt geactiveerd met de nodige argumenten. Het is belangrijk om in gedachten te houden dat de verwachting alleen van toepassing is als een methode wordt aangeroepen met deze exacte argumenten.
Hier zijn een paar voorbeelden.
$ Mock-> shouldReceive ( 'krijgen') -> withAnyArgs () -> één keer (); // de standaard $ mock-> shouldReceive ('get') -> met ('foo.txt') -> once (); $ mock-> shouldReceive ('put') -> with ('foo.txt', 'foo bar') -> once ();Dit kan nog verder worden uitgebreid om de argumentwaarden dynamisch te laten zijn, zolang ze aan bepaalde criteria voldoen. Misschien willen we alleen ervoor zorgen dat een tekenreeks wordt doorgegeven aan een methode:
$ Mock-> shouldReceive ( 'krijgen') -> met (Mockery soort :: ( 'string')) -> één keer ();Of misschien moet het argument overeenkomen met een reguliere expressie. Laten we aannemen dat elke bestandsnaam eindigt met
.tekst
moet worden gekoppeld.$ mockedFile-> shouldReceive ('put') -> with ('/ \. txt $ /', Mockery :: any ()) -> once ();En als een laatste (maar niet beperkt tot) voorbeeld laten we een reeks acceptabele waarden toestaan, met behulp van de
anyof
matcher.$ mockedFile-> shouldReceive ('get') -> with (Mockery :: anyOf ('log.txt', 'cache.txt')) -> once ();Met deze code is de verwachting alleen van toepassing als het eerste argument voor de
krijgen
methode islog.txt
ofcache.txt
. Anders wordt er een uitzondering Mockery gegenereerd wanneer de tests worden uitgevoerd.Mockery \ Exception \ NoMatchingExpectationException: Geen overeenkomende handler gevonden ...Tip: Vergeet niet dat je altijd alias kunt zijn
Spot
zoalsm
aan de top van je klas om dingen een beetje beknopter te maken:gebruik spot als m;
. Dit maakt het beknopter,m :: mock ()
.Ten slotte hebben we verschillende opties om aan te geven wat de bespotte methode moet doen of teruggeven. Misschien hebben we het alleen nodig om een boolean te retourneren. Gemakkelijk:
$ mock-> shouldReceive ('methode') -> once () -> andReturn (false);
Gedeeltelijke mops
U zult merken dat er situaties zijn waarin u slechts één methode hoeft te bespotten in plaats van het hele object. Laten we ons in dit voorbeeld voorstellen dat een methode in uw klas verwijst naar een aangepaste globale functie (hijg) om een waarde uit een configuratiebestand op te halen.
GetOption ( 'timeout'); // doe iets met $ timeoutHoewel er een paar verschillende technieken zijn voor het bespotten van algemene functies. desondanks is het het beste om te voorkomen dat deze methode alles samen roept. Dit is precies wanneer gedeeltelijke moppen in het spel komen.
public function testPartialMockExample () $ mock = Mockery :: mock ('MyClass [getOption]'); $ mock-> shouldReceive ('getOption') -> once () -> andReturn (10000); $ Mock-> brand ();Let op hoe we de methode tussen haakjes hebben geplaatst om te spotten. Als u meerdere methoden heeft, scheidt u deze eenvoudig met een komma, zoals:
$ mock = Spot: :: mock ('MyClass [methode1, methode2]');Met deze techniek zullen de rest van de methoden op het object worden geactiveerd en zich gedragen zoals ze normaal zouden doen. Houd in gedachten dat je altijd het gedrag van je bespotte methoden moet verklaren, zoals we hierboven hebben gedaan. In dit geval, wanneer
GetOption
wordt geroepen, in plaats van de code erin uit te voeren, keren we gewoon terug10000
.Een alternatieve optie is om gebruik te maken van passieve partiële mocks, die je kunt bedenken als het instellen van een standaardstatus voor het mock-object: alle methoden die worden uitgesteld naar de hoofdouderklasse, tenzij een verwachting is opgegeven.
Het vorige codefragment kan worden herschreven als:
public function testPassiveMockExample () $ mock = Mockery :: mock ('MyClass') -> makePartial (); $ mock-> shouldReceive ('getOption') -> once () -> andReturn (10000); $ Mock-> brand ();In dit voorbeeld zijn alle methoden ingeschakeld
Mijn klas
zullen zich gedragen zoals ze normaal zouden doen, met uitzondering vanGetOption
, die zal worden bespot en terug 10000 '.
Hamcrest
De Hamcrest-bibliotheek biedt een extra set matchers voor het definiëren van verwachtingen.Als u eenmaal vertrouwd bent gemaakt met de Mockery API, is het aanbevolen dat u ook gebruik maakt van de Hamcrest-bibliotheek, die een extra set matchers biedt voor het definiëren van leesbare verwachtingen. Net als Mockery kan het worden geïnstalleerd via Composer.
"require-dev": "spot / spot": "dev-master", "davedevelopment / hamcrest-php": "dev-master"Na de installatie kunt u een beter leesbare notatie gebruiken om uw tests te definiëren. Hieronder vindt u enkele voorbeelden, waaronder kleine variaties die hetzelfde eindresultaat opleveren.
Merk op hoe Hamcrest je toestaat je beweringen op een leesbare of beknopte manier naar eigen inzicht te schrijven. Het gebruik van de
is ()
functie is niets meer dan syntactische suiker om de leesbaarheid te bevorderen.Je zult merken dat Mockery heel goed mengt met Hamcrest. Bijvoorbeeld, alleen met Mockery, om te specificeren dat een bespotte methode moet worden aangeroepen met een enkel argument van het type,
draad
, je zou kunnen schrijven:$ mock-> shouldReceive ('methode') -> met (Mockery :: type ('string')) -> once ();Als u Hamcrest gebruikt,
Mockery ::-type
kan worden vervangen doortekenreekswaarde()
, zoals zo:$ mock-> shouldReceive ('methode') -> met (stringValue ()) -> once ();Hamcrest volgt de hulpbronBenoemingsconventie voor waarden om overeen te komen met het type waarde.
nullvalue
integere waarde
arrayValue
Als alternatief, om elk argument te evenaren, Mockery :: enige ()
zou kunnen worden iets()
.
$ file-> shouldReceive ('put') -> with ('foo.txt', anything ()) -> once ();
De grootste hindernis voor het gebruik van Mockery is ironisch genoeg niet de API zelf.
De grootste hindernis voor het gebruik van Mockery is, ironisch genoeg, niet de API zelf, maar begrijp waarom en wanneer je nep gebruikt in je testen.
De sleutel is om te leren en het principe van enkele verantwoordelijkheid te respecteren in uw codeerworkflow. Bedacht door Bob Martin, de SRP dicates dat een klasse "zou één, en slechts één reden moeten hebben om te veranderen."Met andere woorden, een klasse hoeft niet te worden bijgewerkt als reactie op meerdere, niet-gerelateerde wijzigingen in uw toepassing, zoals het aanpassen van bedrijfslogica, of hoe uitvoer wordt geformatteerd, of hoe gegevens kunnen worden bewaard. als een methode zou een klasse één ding moeten doen.
De het dossier
class beheert bestandsysteeminteracties. EEN MySQLdb
repository blijft data behouden. Een E-mail
klas bereidt en verzendt e-mails. Merk op hoe in geen van deze voorbeelden het woord was, en, gebruikt.
Zodra dit wordt begrepen, wordt het testen aanzienlijk eenvoudiger. Afhankelijkheid injectie moet worden gebruikt voor alle operaties die niet onder de klasse vallen paraplu. Tijdens het testen, focus je op één klasse tegelijk en bespreek je alle afhankelijkheden. Je bent niet geïnteresseerd in hoe dan ook; ze hebben hun eigen tests!
Hoewel niets je ervan weerhoudt gebruik te maken van de native mocking-implementatie van PHPUnit, waarom zou je je dan zorgen maken wanneer de verbeterde leesbaarheid van Mockery slechts een update van de componist
weg?