Spott Ein besserer Weg

Mockery ist eine PHP-Erweiterung, die vor allem im Vergleich zu PHPUnit ein hervorragendes Mock-Erlebnis bietet. Während das spöttische Framework von PHPUnit mächtig ist, bietet Mockery eine natürlichere Sprache mit einem Hamcrest-ähnlichen Satz von Matchern. In diesem Artikel vergleiche ich die beiden spöttischen Frameworks und hebe die besten Eigenschaften von Mockery hervor.

Mockery bietet eine Reihe spaßbezogener Matcher an, die einem Hamcrest-Wörterbuch sehr ähnlich sind und eine sehr natürliche Art und Weise bieten, um verspottete Erwartungen auszudrücken. Mockery überschreibt nicht die in PHPUnit integrierten Spottfunktionen und steht nicht in Konflikt mit diesen. Sie können beide gleichzeitig (und sogar in derselben Testmethode) verwenden..


Mockery installieren

Es gibt mehrere Möglichkeiten, Mockery zu installieren. Hier sind die gängigsten Methoden.

Composer verwenden

Erstellen Sie eine Datei mit dem Namen composer.json im Stammordner Ihres Projekts und fügen Sie der Datei den folgenden Code hinzu:

"erfordern": "Spott / Spott": "> = 0.7.2"

Als Nächstes installieren Sie Composer einfach mit dem folgenden Befehl im Stammordner Ihres Projekts:

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

Installieren Sie abschließend alle erforderlichen Abhängigkeiten (einschließlich Mockery) mit diesem Befehl:

php composer.phar installieren

Wenn alles installiert ist, stellen wir sicher, dass unsere Mockery-Installation funktioniert. Der Einfachheit halber gehe ich davon aus, dass Sie einen Ordner haben, genannt Prüfung im Stammverzeichnis des Projekts. Alle Beispiele in diesem Lernprogramm befinden sich in diesem Ordner. Hier ist der Code, den ich verwendet habe, um sicherzustellen, dass Mockery mit meinem Projekt funktioniert:

// Dateiname: JustToCheckMockeryTest.php requir_once '… /vendor/autoload.php'; Klasse JustToCheckMockeryTest erweitert PHPUnit_Framework_TestCase protected function tearDown () \ Mockery :: close ();  function testMockeryWorks () $ mock = \ Mockery :: mock ('AClassToBeMocked'); $ mock-> shouldReceive ('someMethod') -> once (); $ workerObject = new AClassToWorkWith; $ workerObject-> doSomethingWit ($ mock);  Klasse AClassToBeMocked  Klasse AClassToWorkWith Funktion doSomethingWit ($ anotherClass) return $ anotherClass-> someMethod (); 

Linux-Benutzer: Verwenden Sie die Pakete Ihrer Distribution

Einige Linux-Distributionen erleichtern die Installation von Mockery, aber nur eine Handvoll stellt ein Mockery-Paket für ihr System bereit. Die folgende Liste ist die einzige, die mir bekannt ist:

  • Sabayon: Installieren Sie Mockery
  • Fedora / RHE: yum installiere Spott

BIRNE verwenden

PEAR-Fans können Mockery mit den folgenden Befehlen installieren:

Sudo-Birne-Kanal entdecken Birne.survivethedeepend.com Sudo-Birne-Kanal entdecken Hamcrest.googlecode.com/svn/pear Sudo Birne installieren - alldeps deepend / Mockery

Installation von der Quelle

Die Installation von GitHub ist für echte Geeks! Sie können die neueste Version von Mockery jederzeit über das GitHub-Repository abrufen.

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

Unser erstes verspottetes Objekt erstellen

Machen wir uns über einige Objekte lustig, bevor wir Erwartungen definieren. Mit dem folgenden Code wird das vorherige Beispiel so geändert, dass es sowohl Beispiele für PHPUnit als auch Mockery enthält:

// Dateiname: MockeryABetterWayOfMockingTest.php requir_once '… /vendor/autoload.php'; class MockeryVersusPHPUnitGetMockTest erweitert PHPUnit_Framework_TestCase Funktion testCreateAMockedObject () // Mit PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); // Mit Mockery $ mockeryMock = \ Mockery :: mock ('AClassToBeMocked');  Klasse AClassToBeMocked 

Mit Mockery können Sie Mocks für Klassen definieren, die nicht vorhanden sind.

Die erste Zeile stellt sicher, dass wir Zugang zu Mockery haben. Als Nächstes erstellen wir eine Testklasse namens SpottVersusPHPUnitGetMockTest, das hat eine Methode, testCreateAMockedObject (). Die verspottete Klasse, AClassToBeMocked, ist zu diesem Zeitpunkt völlig leer; Sie könnten die Klasse vollständig entfernen, ohne dass der Test fehlschlägt.

Das testCreateAMockedObject () Testmethode definiert zwei Objekte. Der erste ist ein PHPUnit-Mock, und der zweite wird mit Mockery erstellt. Mockery's Syntax lautet:

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

Weisen Sie einfache Erwartungen zu

Mocks werden im Allgemeinen verwendet, um das Verhalten eines Objekts (hauptsächlich seine Methoden) zu überprüfen, indem angegeben wird, was aufgerufen wird Erwartungen. Lassen Sie uns ein paar einfache Erwartungen aufstellen.

Erwarten Sie, dass eine Methode aufgerufen wird

Die häufigste Erwartung ist wahrscheinlich eine, die einen bestimmten Methodenaufruf erwartet. Bei den meisten Mocking-Frameworks können Sie die Anzahl der Aufrufe angeben, die eine Methode erwarten soll. Wir beginnen mit einer einfachen Erwartung eines einzelnen Anrufs:

// Dateiname: MockeryABetterWayOfMockingTest.php requir_once '… /vendor/autoload.php'; class MockeryVersusPHPUnitGetMockTest erweitert PHPUnit_Framework_TestCase protected function tearDown () \ Mockery :: close ();  function testExpectOnce () $ someObject = new SomeClass (); // Mit PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ phpunitMock-> erwartet ($ this-> once ()) -> method ('someMethod'); // Übung für PHPUnit $ someObject-> doSomething ($ phpunitMock); // Mit Mockery $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ mockeryMock-> shouldReceive ('someMethod') -> once (); // Übung für Spott $ someObject-> doSomething ($ mockeryMock);  class AClassToBeMocked function someMethod ()  class SomeClass function doSomething ($ ein anderesObjekt) $ anotherObject-> someMethod (); 

Dieser Code konfiguriert eine Erwartung sowohl für PHPUnit als auch für Mockery. Beginnen wir mit dem ersteren.

Einige Linux-Distributionen erleichtern die Installation von Mockery.

Wir nehmen das erwartet () Methode zum Definieren einer Erwartung zum Aufrufen someMethod () Einmal. Damit PHPUnit korrekt funktioniert, müssen wir Muss Definiere eine Klasse namens AClassToBeMocked, und es muss eine haben someMethod () Methode.

Das ist ein Problem. Wenn Sie viele Objekte verspotten und nach TDD-Prinzipien für ein Top-Down-Design entwickeln, möchten Sie nicht vor dem Test alle Klassen und Methoden erstellen. Ihr Test sollte aus dem richtigen Grund fehlschlagen, dass die erwartete Methode nicht aufgerufen wurde, anstatt einen kritischen PHP-Fehler ohne Bezug zur realen Implementierung. Fahren Sie fort und versuchen Sie, das zu entfernen someMethod () Definition aus AClassToBeMocked, und sehen was passiert.

Auf der anderen Seite können Sie mit Mockery Mock für Klassen definieren, die nicht vorhanden sind.

Beachten Sie, dass das obige Beispiel einen Mock für erstellt AnInexistentClass, was, wie der Name schon sagt, nicht existiert (und auch nicht sein someMethod () Methode).

Am Ende des obigen Beispiels definieren wir die SomeClass Klasse, um unseren Code auszuüben. Wir initialisieren ein Objekt, das aufgerufen wird $ someObject in der ersten Zeile der Testmethode, und wir üben den Code effektiv aus, nachdem wir unsere Erwartungen definiert haben.

Bitte beachten Sie: Mockery bewertet die Erwartungen schließen() Methode. Aus diesem Grund sollten Sie immer eine niederreissen() Methode in Ihrem Test, der aufruft \ Mockery :: close (). Ansonsten gibt Mockery falsch positive Ergebnisse.

Erwarten Sie mehr als einen Anruf

Wie bereits erwähnt, können die meisten Mocking-Frameworks die Erwartungen für mehrere Methodenaufrufe angeben. PHPUnit verwendet das $ this-> genau () Konstruieren Sie für diesen Zweck. Der folgende Code definiert die Erwartungen für den mehrmaligen Aufruf einer Methode:

function testExpectMultiple () $ someObject = new SomeClass (); // Mit PHPUnit 2 mal $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ phpunitMock-> erwartet ($ this-> genau (2)) -> Methode ('someMethod'); // Übung für PHPUnit $ someObject-> doSomething ($ phpunitMock); $ someObject-> doSomething ($ phpunitMock); // Mit Mockery 2 mal $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ mockeryMock-> shouldReceive ('someMethod') -> zweimal (); // Übung für Spott $ someObject-> doSomething ($ mockeryMock); $ someObject-> doSomething ($ mockeryMock); // Mit Mockery dreimal $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ mockeryMock-> shouldReceive ('someMethod') -> times (3); // Übung für Spott $ someObject-> doSomething ($ mockeryMock); $ someObject-> doSomething ($ mockeryMock); $ someObject-> doSomething ($ mockeryMock); 

Mockery bietet zwei verschiedene Methoden, um Ihre Anforderungen besser zu erfüllen. Die erste Methode, zweimal(), erwartet zwei Methodenaufrufe. Die andere Methode ist mal(), Damit können Sie einen Betrag angeben. Mockery's Ansatz ist viel sauberer und leichter lesbar.


Werte zurückgeben

Eine weitere übliche Verwendung für Mocks ist das Testen des Rückgabewerts einer Methode. Natürlich haben sowohl PHPUnit als auch Mockery die Mittel, um Rückgabewerte zu überprüfen. Lassen Sie uns noch einmal mit etwas Einfachem beginnen.

Einfache Rückgabewerte

Der folgende Code enthält sowohl PHPUnit- als auch Mockery-Code. Ich habe auch aktualisiert SomeClass einen überprüfbaren Rückgabewert liefern.

class MockeryVersusPHPUnitGetMockTest erweitert PHPUnit_Framework_TestCase protected function tearDown () \ Mockery :: close ();  // […] // function testSimpleReturnValue () $ someObject = new SomeClass (); $ someValue = 'irgendein Wert'; // Mit PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ phpunitMock-> erwartet ($ this-> once ()) -> Methode ('someMethod') -> wird ($ this-> returnValue ($ someValue)); // Erwarte den zurückgegebenen Wert $ this-> assertEquals ($ someValue, $ someObject-> doSomething ($ phpunitMock)); // Mit Mockery $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ mockeryMock-> shouldReceive ('someMethod') -> once () -> andReturn ($ someValue); // Erwarte den Rückgabewert $ this-> assertEquals ($ someValue, $ someObject-> doSomething ($ mockeryMock));  class AClassToBeMocked function someMethod ()  class SomeClass function doSomething ($ ein anderesObjekt) return $ anotherObject-> someMethod (); 

Die API von PHPUnit und Mockery ist unkompliziert und einfach zu bedienen, aber ich finde Mockery immer noch sauberer und lesbarer.

Verschiedene Werte zurückgeben

Häufige Unit-Tester können mit Methoden, die unterschiedliche Werte zurückgeben, Komplikationen feststellen. Leider ist PHPUnit begrenzt $ this-> at ($ index) Methode ist die nur So können Sie unterschiedliche Werte aus derselben Methode zurückgeben. Der folgende Code veranschaulicht das beim() Methode:

function testDemonstratePHPUnitCallIndexing () $ someObject = new SomeClass (); $ firstValue = 'erster Wert'; $ secondValue = 'zweiter Wert'; // Mit PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ phpunitMock-> erwartet ($ this-> at (0)) -> Methode ('someMethod') -> wird ($ this-> returnValue ($ firstValue)); $ phpunitMock-> erwartet ($ this-> at (1)) -> Methode ('someMethod') -> wird ($ this-> returnValue ($ secondValue)); // Erwarte den zurückgegebenen Wert $ this-> assertEquals ($ firstValue, $ someObject-> doSomething ($ phpunitMock)); $ this-> assertEquals ($ secondValue, $ someObject-> doSomething ($ phpunitMock)); 

Dieser Code definiert zwei separate Erwartungen und ruft zwei unterschiedliche Aufrufe an someMethod (); also ist dieser test bestanden. Aber lassen Sie uns eine Wendung einführen und einen Doppelruf in der getesteten Klasse hinzufügen:

 // […] // function testDemonstratePHPUnitCallIndexingOnTheSameClass () $ someObject = new SomeClass (); $ firstValue = 'erster Wert'; $ secondValue = 'zweiter Wert'; // Mit PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ phpunitMock-> erwartet ($ this-> at (0)) -> Methode ('someMethod') -> wird ($ this-> returnValue ($ firstValue)); $ phpunitMock-> erwartet ($ this-> at (1)) -> Methode ('someMethod') -> wird ($ this-> returnValue ($ secondValue)); // Erwarte den zurückgegebenen Wert $ this-> assertEquals ('erster Wert zweiter Wert', $ someObject-> concatenate ($ phpunitMock));  class SomeClass function doSomething ($ anotherObject) return $ anotherObject-> someMethod ();  Funktion concatenate ($ anotherObject) return $ anotherObject-> someMethod (). ". $ anotherObject-> someMethod ();

Der Test ist immer noch bestanden. PHPUnit erwartet zwei Aufrufe von someMethod () Dies geschieht innerhalb der getesteten Klasse, wenn die Verkettung über die durchgeführt wird verketten() Methode. Der erste Aufruf gibt den ersten Wert und der zweite Aufruf den zweiten Wert zurück. Aber hier ist der Haken: Was würde passieren, wenn Sie die Behauptung verdoppeln? Hier ist der Code:

function testDemonstratePHPUnitCallIndexingOnTheSameClass () $ someObject = new SomeClass (); $ firstValue = 'erster Wert'; $ secondValue = 'zweiter Wert'; // Mit PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ phpunitMock-> erwartet ($ this-> at (0)) -> Methode ('someMethod') -> wird ($ this-> returnValue ($ firstValue)); $ phpunitMock-> erwartet ($ this-> at (1)) -> Methode ('someMethod') -> wird ($ this-> returnValue ($ secondValue)); // Erwarte den zurückgegebenen Wert $ this-> assertEquals ('erster Wert zweiter Wert', $ someObject-> concatenate ($ phpunitMock)); $ this-> assertEquals ('erster Wert zweiter Wert', $ someObject-> concatenate ($ phpunitMock)); 

Es gibt den folgenden Fehler zurück:

Fehler beim Bestätigen, dass zwei Zeichenfolgen gleich sind. --- Erwartet +++ Tatsächlicher @@ @@ - erster Wert zweiter Wert '+ "

PHPUnit zählt weiterhin zwischen verschiedenen Aufrufen von verketten(). Zu dem Zeitpunkt tritt der zweite Anruf in der letzten Behauptung auf, $ index ist bei den Werten 2 und 3. Sie können den Test bestehen, indem Sie Ihre Erwartungen ändern, um die zwei neuen Schritte wie folgt zu berücksichtigen:

function testDemonstratePHPUnitCallIndexingOnTheSameClass () $ someObject = new SomeClass (); $ firstValue = 'erster Wert'; $ secondValue = 'zweiter Wert'; // Mit PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ phpunitMock-> erwartet ($ this-> at (0)) -> Methode ('someMethod') -> wird ($ this-> returnValue ($ firstValue)); $ phpunitMock-> erwartet ($ this-> at (1)) -> Methode ('someMethod') -> wird ($ this-> returnValue ($ secondValue)); $ phpunitMock-> erwartet ($ this-> at (2)) -> Methode ('someMethod') -> wird ($ this-> returnValue ($ firstValue)); $ phpunitMock-> erwartet ($ this-> at (3)) -> Methode ('someMethod') -> wird ($ this-> returnValue ($ secondValue)); // Erwarte den zurückgegebenen Wert $ this-> assertEquals ('erster Wert zweiter Wert', $ someObject-> concatenate ($ phpunitMock)); $ this-> assertEquals ('erster Wert zweiter Wert', $ someObject-> concatenate ($ phpunitMock)); 

Sie können wahrscheinlich mit diesem Code leben, aber Mockery macht dieses Szenario trivial. Glaub mir nicht Schau mal:

function testMultipleReturnValuesWithMockery () $ someObject = new SomeClass (); $ firstValue = 'erster Wert'; $ secondValue = 'zweiter Wert'; // Mit Mockery $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ mockeryMock-> sollteReceive ('someMethod') -> andReturn ($ firstValue, $ secondValue, $ firstValue, $ secondValue); // Erwarte den zurückgegebenen Wert $ this-> assertEquals ('erster Wert zweiter Wert', $ someObject-> concatenate ($ mockeryMock)); $ this-> assertEquals ('erster Wert zweiter Wert', $ someObject-> concatenate ($ mockeryMock)); 

Wie PHPUnit verwendet Mockery die Indexzählung, aber wir müssen uns keine Sorgen um Indizes machen. Stattdessen listen wir einfach alle erwarteten Werte auf und Mockery gibt sie der Reihe nach zurück.

Zusätzlich gibt PHPUnit zurück NULL für nicht festgelegte Indizes, Mockery gibt jedoch immer den zuletzt angegebenen Wert zurück. Das ist eine nette Geste.

Versuchen Sie mehrere Methoden mit der Indizierung

Lassen Sie uns eine zweite Methode in unseren Code einführen, die concatWithMinus () Methode:

class SomeClass function doSomething ($ ein anderesObjekt) return $ anotherObject-> someMethod ();  Funktion concatenate ($ anotherObject) return $ anotherObject-> someMethod (). ". $ anotherObject-> someMethod (); Funktion concatWithMinus ($ anotherObject) return $ anotherObject-> anotherMethod (). '-'. $ anotherObject -> anotherMethod ();

Diese Methode verhält sich ähnlich wie verketten(), aber es verkettet die String-Werte mit " - "Im Gegensatz zu einem einzelnen Bereich. Da diese beiden Methoden ähnliche Aufgaben ausführen, ist es sinnvoll, sie innerhalb derselben Testmethode zu testen, um doppelte Tests zu vermeiden.

Wie im obigen Code gezeigt, verwendet die zweite Funktion eine andere, aufgerufene Methode eine andere Methode(). Ich habe diese Änderung vorgenommen, um uns zu zwingen, in unseren Tests beide Methoden zu verspotten. Unsere spottbare Klasse sieht jetzt so aus:

Klasse AClassToBeMocked Funktion someMethod ()  Funktion anotherMethod () 

Das Testen mit PHPUnit könnte folgendermaßen aussehen:

function testPHPUnitIndexingOnMultipleMethods () $ someObject = new SomeClass (); $ firstValue = 'erster Wert'; $ secondValue = 'zweiter Wert'; // Mit PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); // Erster und zweiter Aufruf der semeMethod: $ phpunitMock-> Expects ($ this-> at (0)) -> Methode ('someMethod') -> will ($ this-> returnValue ($ firstValue)); $ phpunitMock-> erwartet ($ this-> at (1)) -> Methode ('someMethod') -> wird ($ this-> returnValue ($ secondValue)); // Erwarte den zurückgegebenen Wert $ this-> assertEquals ('erster Wert zweiter Wert', $ someObject-> concatenate ($ phpunitMock)); // Erster und zweiter Aufruf der anotherMethod: $ phpunitMock-> Expects ($ this-> at (0)) -> Methode ('anotherMethod') -> will ($ this-> returnValue ($ firstValue)); $ phpunitMock-> erwartet ($ this-> at (1)) -> Methode ('anotherMethod') -> wird ($ this-> returnValue ($ secondValue)); // Erwarte den Rückgabewert $ this-> assertEquals ('erster Wert - zweiter Wert', $ someObject-> concatWithMinus ($ phpunitMock)); 

Die Logik ist solide. Definieren Sie für jede Methode zwei unterschiedliche Erwartungen und geben Sie den Rückgabewert an. Dies funktioniert nur mit PHPUnit 3.6 oder neuer.

Bitte beachten Sie: PHPunit 3.5 und älter hatten einen Fehler, durch den der Index für jede Methode nicht zurückgesetzt wurde. Dies führte zu unerwarteten Rückgabewerten für übermittelte Methoden.

Schauen wir uns das gleiche Szenario mit Mockery an. Wieder erhalten wir viel saubereren Code. Überzeugen Sie sich selbst:

function testMultipleReturnValuesForDifferentFunctionsWithMockery () $ someObject = new SomeClass (); $ firstValue = 'erster Wert'; $ secondValue = 'zweiter Wert'; // Mit Mockery $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ mockeryMock-> sollteReceive ('someMethod') -> andReturn ($ firstValue, $ secondValue); $ mockeryMock-> sollteReceive ('anotherMethod') -> andReturn ($ firstValue, $ secondValue); // Erwarte den zurückgegebenen Wert $ this-> assertEquals ('erster Wert zweiter Wert', $ someObject-> concatenate ($ mockeryMock)); $ this-> assertEquals ('erster Wert - zweiter Wert', $ someObject-> concatWithMinus ($ mockeryMock)); 

Rückgabewerte basierend auf den angegebenen Parametern

Ehrlich gesagt, das kann PHPUnit einfach nicht. Zum Zeitpunkt dieses Schreibens erlaubt PHPUnit nicht, unterschiedliche Werte von derselben Funktion basierend auf dem Parameter der Funktion zurückzugeben. Daher schlägt der folgende Test fehl:

 // […] // function testPHUnitCandDecideByParameter () $ someObject = new SomeClass (); // Mit PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ phpunitMock-> erwartet ($ this-> any ()) -> Methode ('getNumber') -> mit (2) -> wird ($ this-> returnValue (2)); $ phpunitMock-> erwartet ($ this-> any ()) -> Methode ('getNumber') -> mit (3) -> wird ($ this-> returnValue (3)); $ this-> assertEquals (4, $ someObject-> doubleNumber ($ phpunitMock, 2)); $ this-> assertEquals (6, $ someObject-> doubleNumber ($ phpunitMock, 3));  class AClassToBeMocked // […] // Funktion getNumber ($ number) return $ number;  class SomeClass // […] // Funktion doubleNumber ($ anotherObject, $ number) return $ anotherObject-> getNumber ($ number) * 2; 

Bitte ignorieren Sie die Tatsache, dass es in diesem Beispiel keine Logik gibt. es würde auch scheitern, wenn es anwesend wäre. Dieser Code hilft jedoch, die Idee zu veranschaulichen.

Dieser Test schlägt fehl, da PHPUnit die beiden Erwartungen im Test nicht unterscheiden kann. Die zweite Erwartung erwartet den Parameter 3, überschreibt einfach den ersten erwarteten Parameter 2. Wenn Sie versuchen, diesen Test auszuführen, wird folgende Fehlermeldung angezeigt:

Die Erwartung ist für den Methodennamen fehlgeschlagen  bei null oder mehrmaligem Aufruf Parameter 0 für Aufruf AClassToBeMocked :: getNumber (2) stimmt nicht mit dem erwarteten Wert überein. Fehler beim Bestätigen, dass 2 Übereinstimmungen erwartet werden 3.

Mockery kann dies tun, und der folgende Code funktioniert genauso, wie Sie es erwarten würden. Die Methode gibt basierend auf den angegebenen Parametern unterschiedliche Werte zurück:

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

Teilspötter

Manchmal möchten Sie nur bestimmte Methoden für Ihr Objekt simulieren (anstatt ein gesamtes Objekt zu verspotten). Folgende Taschenrechner Klasse existiert bereits; Wir möchten nur bestimmte Methoden verspotten:

Klassenrechner Funktion add ($ firstNo, $ secondNo) return $ firstNo + $ secondNo;  Funktion subtrahieren ($ firstNo, $ secondNo) return $ firstNo - $ secondNo;  Funktion multiplizieren ($ value, $ multiplier) $ newValue = 0; für ($ i = 0; $ i<$multiplier;$i++) $newValue = $this->add ($ newValue, $ value); return $ newValue; 

Diese Taschenrechner Klasse hat drei Methoden: hinzufügen(), subtrahieren(), und multiplizieren(). Multiplikation verwendet eine Schleife, um die Multiplikation durch Aufrufen von durchzuführen hinzufügen() für eine bestimmte Anzahl von Zeiten (z. 2 x 3 ist wirklich 2 + 2 + 2).

Nehmen wir an, dass wir testen wollen multiplizieren() in totaler Isolation; also werden wir verspotten hinzufügen() und auf spezifisches Verhalten prüfen multiplizieren(). Hier sind einige mögliche Tests:

Funktion testPartialMocking () $ value = 3; $ Multiplikator = 2; $ result = 6; // PHPUnit $ phpMock = $ this-> getMock ('Calculator', Array ('Add')); $ phpMock-> erwartet ($ this-> genau (2)) -> Methode ('add') -> wird ($ this-> returnValue ($ result)); $ this-> assertEquals ($ result, $ phpMock-> multipliply ($ value, $ multiplier)); // Mockery $ mockeryMock = \ Mockery :: mock (neuer Rechner); $ mockeryMock-> shouldReceive ('add') -> andReturn ($ result); $ this-> assertEquals ($ result, $ mockeryMock-> multipliply ($ value, $ multiplier)); // Mockery erweiterte Testprüfungsparameter $ mockeryMock2 = \ Mockery :: mock (neuer Rechner); $ mockeryMock2-> sollteReceive ('add') -> mit (0,3) -> andReturn (3); $ mockeryMock2-> sollteReceive ('add') -> mit (3,3) -> andReturn (6); $ this-> assertEquals ($ result, $ mockeryMock2-> multiply ($ value, $ multiplier)); 

Spott bietet… einen sehr natürlichen Weg, um verspottete Erwartungen auszudrücken.

Der erste PHPUnit-Test ist anämisch. es testet einfach die Methode hinzufügen() wird zweimal aufgerufen und gibt bei jedem Aufruf den endgültigen Wert zurück. Es erledigt die Arbeit, ist aber auch etwas kompliziert. PHPUnit zwingt Sie, die Liste der Methoden, die Sie als zweiten Parameter simulieren möchten, zu übergeben $ this-> getMock (). Andernfalls würde PHPUnit alle Methoden verspotten, von denen jede zurückkehrt NULL standardmäßig. Diese Liste Muss Halten Sie sich an die Erwartungen, die Sie an Ihr Spottobjekt definieren.

Zum Beispiel, wenn ich eine zweite Erwartung hinzufüge $ phpMock's subtrahieren () Diese Methode ignoriert PHPUnit und ruft das Original auf subtrahieren () Methode. Das heißt, es sei denn, ich gebe ausdrücklich den Namen der Methode an (subtrahieren) in dem $ this-> getmock () Aussage.

Mockery unterscheidet sich natürlich dadurch, dass Sie ein reales Objekt bereitstellen können \ Mockery :: mock (), und es erstellt automatisch einen partiellen Schein. Dies wird erreicht, indem eine Proxy-ähnliche Lösung für das Spotting implementiert wird. Alle Erwartungen, die Sie definieren, werden verwendet, aber Mockery greift auf die ursprüngliche Methode zurück, wenn Sie keine Erwartung für diese Methode angeben.

Bitte beachten Sie: Der Ansatz von Mockery ist sehr einfach, aber interne Methodenaufrufe durchlaufen das gespielte Objekt nicht.

Dieses Beispiel ist irreführend, aber es veranschaulicht wie man es nicht benutzt Verspottung ist ein partieller Spott. Ja, Mockery erstellt einen partiellen Mock, wenn Sie ein reales Objekt übergeben, aber es spottet nur nur externe anrufe. Zum Beispiel basierend auf dem vorherigen Code der multiplizieren() Methode ruft das Reale auf hinzufügen() Methode. Gehen Sie weiter und versuchen Sie, die letzte Erwartung von zu ändern … -> andReturn (6) zu … -> andReturn (7). Der Test sollte natürlich scheitern, aber nicht der reale hinzufügen() führt statt verspottet aus hinzufügen() Methode.

Wir können dieses Problem jedoch umgehen, indem Sie solche Muster wie folgt erstellen:

// Anstelle von $ mockeryMock = \ Mockery :: mock (neuer Rechner); // Erzeuge den Mock wie folgt: $ mockeryMock = \ Mockery :: mock ('Calculator [add]');

Das syntaktisch unterschiedliche Konzept ähnelt dem Ansatz von PHPUnit: Sie müssen die gespielten Methoden an zwei Stellen auflisten. Bei jedem anderen Test können Sie jedoch einfach das reale Objekt übergeben, was viel einfacher ist - insbesondere bei Konstruktorparametern.


Umgang mit Konstruktorparametern

Fügen wir dem Konstruktor einen Konstruktor mit zwei Parametern hinzu Taschenrechner Klasse. Der überarbeitete Code:

Klassenrechner public $ myNumbers = array (); Funktion __construct ($ firstNo, $ secondNo) $ this-> myNumbers [] = $ firstNo; $ this-> myNumbers [] = $ secondNo;  // […] //

Jeder Test in diesem Artikel schlägt fehl, nachdem dieser Konstruktor hinzugefügt wurde. Genauer gesagt die testPartialMock () Testergebnisse im folgenden Fehler:

Fehlendes Argument 1 für Calculator :: __ construct (), in /usr/share/php/PHPUnit/Framework/MockObject/Generator.php in Zeile 224 aufgerufen und definiert

PHPUnit versucht, das reale Objekt zu verspotten, indem es den Konstruktor automatisch aufruft und erwartet, dass die Parameter richtig eingestellt sind. Es gibt zwei Möglichkeiten, dieses Problem zu umgehen: Legen Sie entweder die Parameter fest oder rufen Sie den Konstruktor nicht auf.

// Konstruktorparameter angeben $ phpMock = $ this-> getMock ('Rechner', Array ('Hinzufügen'), Array (1,2)); // Rufe nicht den ursprünglichen Konstruktor auf $ phpMock = $ this-> getMock ('Calculator', array ('add'), array (), ", false);

Spott automagisch umgeht dieses Problem. Es ist in Ordnung, keinen Konstruktorparameter anzugeben. Mockery ruft den Konstruktor einfach nicht auf. Sie können jedoch eine Liste von Konstruktorparametern angeben, die von Mockery verwendet werden sollen. Zum Beispiel:

function testMockeryConstructorParameters () $ result = 6; // Mockery // Konstruktor nicht aufrufen $ noConstrucCall = \ Mockery :: mock ('Calculator [add]'); $ noConstrucCall-> shouldReceive ('add') -> andReturn ($ result); // Verwenden Sie Konstruktorparameter $ withConstructParams = \ Mockery :: mock ('Calculator [add]') array (1,2)); $ withConstructParams-> shouldReceive ('add') -> andReturn ($ result); // Reales Objekt des Benutzers mit realen Werten und Schein darüber $ realCalculator = new Calculator (1,2); $ mockRealObj = \ Mockery :: mock ($ realCalculator); $ mockRealObj-> shouldReceive ('add') -> andReturn ($ result); 

Technische Überlegungen

Mockery ist eine andere Bibliothek, in die Ihre Tests integriert werden. Vielleicht möchten Sie darüber nachdenken, welche technischen Auswirkungen dies haben kann.

  • Spott braucht viel Speicher. Sie müssen den maximalen Speicher auf 512 MB erhöhen, wenn Sie viele Tests ausführen möchten (z. B. über 1000 Tests mit mehr als 3000 Assertions). Sehen php.ini Dokumentation für weitere Details.
  • Sie müssen Ihre Tests so organisieren, dass sie in separaten Prozessen ausgeführt werden, wenn statische Methoden und statische Methodenaufrufe verspottet werden.
  • Sie können Mockery automatisch in jeden Test laden, indem Sie die Bootstrap-Funktionalität von PHPUnit verwenden (hilfreich, wenn Sie viele Tests haben und sich nicht wiederholen möchten)..
  • Sie können den Anruf an automatisieren \ Mockery :: close () bei jedem Test niederreissen() durch Bearbeitung phpunit.xml.

Schlußfolgerungen

PHPUnit hat sicherlich seine Probleme, vor allem wenn es um Funktionalität und Ausdruckskraft geht. Spott kann Ihre Spott-Erfahrung erheblich verbessern, indem Sie das Schreiben und Verstehen von Tests vereinfachen - aber es ist nicht perfekt (es gibt nichts dergleichen)..

Dieses Tutorial hat viele wichtige Aspekte von Mockery hervorgehoben, aber ehrlich gesagt haben wir die Oberfläche kaum zerkratzt. Machen Sie sich mit dem Github-Repository des Projekts vertraut, um mehr zu erfahren.

Danke fürs Lesen!