Scherno un modo migliore

Mockery è un'estensione PHP che offre un'esperienza di mocking superiore, in particolare rispetto a PHPUnit. Mentre la struttura di derisione di PHPUnit è potente, Mockery offre un linguaggio più naturale con una serie di matcher simile a Hamcrest. In questo articolo, confronterò i due quadri di derisione e evidenzierò le migliori caratteristiche di Mockery.

Mockery offre una serie di matcher mocking che sono molto simili a un dizionario di Hamcrest, offrendo un modo molto naturale per esprimere aspettative irrisolte. Mockery non sovrascrive o è in conflitto con le funzioni di simulazione incorporata di PHPUnit; infatti, puoi usarli entrambi contemporaneamente (e anche nello stesso metodo di prova).


Installare Mockery

Esistono diversi modi per installare Mockery; ecco i metodi più comuni.

Usa compositore

Crea un file chiamato composer.json nella cartella principale del progetto e aggiungere il seguente codice a tale file:

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

Quindi, installa semplicemente Composer nella cartella principale del tuo progetto usando il seguente comando:

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

Infine, installa le eventuali dipendenze richieste (incluso Mockery) con questo comando:

installazione php composer.phar

Con tutto installato, assicuriamoci che la nostra installazione di Mockery funzioni. Per semplicità, presumo che tu abbia una cartella, chiamata Test nella directory principale del tuo progetto. Tutti gli esempi in questo tutorial risiederanno in questa cartella. Ecco il codice che ho usato per garantire che Mockery funzioni con il mio progetto:

// Nome file: JustToCheckMockeryTest.php require_once '... /vendor/autoload.php'; classe JustToCheckMockeryTest estende PHPUnit_Framework_TestCase funzione protetta tearDown () \ Mockery :: close ();  function testMockeryWorks () $ mock = \ Mockery :: mock ('AClassToBeMocked'); $ Mock> shouldReceive ( 'someMethod') -> una volta (); $ workerObject = new AClassToWorkWith; $ WorkerObject-> doSomethingWit ($ finto);  classe AClassToBeMocked  class AClassToWorkWith function doSomethingWit ($ anotherClass) return $ anotherClass-> someMethod (); 

Utenti Linux: usa i pacchetti del tuo Distro

Alcune distribuzioni Linux facilitano l'installazione di Mockery, ma solo una manciata fornisce un pacchetto Mockery per il proprio sistema. Il seguente elenco sono le uniche distro di cui sono a conoscenza:

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

Usa la PERA

I fan di PEAR possono installare Mockery emettendo i seguenti comandi:

sudo pera canale-scopri pear.survivethedeepend.com sudo canale pera-scopri hamcrest.googlecode.com/svn/pear sudo pera installa --alldeps deepend / Mockery

Installazione da origine

L'installazione da GitHub è per i veri fanatici! Puoi sempre prendere l'ultima versione di Mockery attraverso il suo repository GitHub.

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

Creare il nostro primo oggetto deriso

Prendiamo in giro alcuni oggetti prima di definire le aspettative. Il codice seguente modificherà l'esempio precedente per includere entrambi gli esempi di PHPUnit e Mockery:

// Nome file: MockeryABetterWayOfMockingTest.php require_once '... /vendor/autoload.php'; class MockeryVersusPHPUnitGetMockTest estende PHPUnit_Framework_TestCase function testCreateAMockedObject () // Con PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); // Con Mockery $ mockeryMock = \ Mockery :: mock ('AClassToBeMocked');  class AClassToBeMocked 

Mockery ti consente di definire i mock per le classi che non esistono.

La prima linea garantisce che abbiamo accesso a Mockery. Successivamente, creiamo una classe di test, chiamata MockeryVersusPHPUnitGetMockTest, che ha un metodo, testCreateAMockedObject (). La classe derisa, AClassToBeMocked, è completamente vuoto in questo momento; infatti, è possibile rimuovere completamente la classe senza causare il fallimento del test.

Il testCreateAMockedObject () il metodo di prova definisce due oggetti. Il primo è un PHPUnit mock e il secondo è creato con Mockery. La sintassi di Mockery è:

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

Assegna semplici aspettative

I mock sono comunemente usati per verificare il comportamento di un oggetto (principalmente i suoi metodi) specificando ciò che viene chiamato aspettative. Facciamo alcune semplici aspettative.

Aspettatevi un metodo per essere chiamato

Probabilmente l'aspettativa più comune è quella che si aspetta una chiamata al metodo specifico. La maggior parte dei framework di simulazione consente di specificare la quantità di chiamate che ci si aspetta da un metodo. Inizieremo con una semplice aspettativa per singola chiamata:

// Nome file: MockeryABetterWayOfMockingTest.php require_once '... /vendor/autoload.php'; class MockeryVersusPHPUnitGetMockTest estende PHPUnit_Framework_TestCase funzione protetta tearDown () \ Mockery :: close ();  function testExpectOnce () $ someObject = new SomeClass (); // Con PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> si aspetta ($ this-> una volta ()) -> Metodo ( 'someMethod'); // Esercizio per PHPUnit $ someObject-> doSomething ($ phpunitMock); // Con Mockery $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ MockeryMock-> shouldReceive ( 'someMethod') -> una volta (); // Esercizio per Mockery $ someObject-> doSomething ($ mockeryMock);  classe AClassToBeMocked function someMethod ()  class SomeClass function doSomething ($ anotherObject) $ anotherObject-> someMethod (); 

Questo codice configura un'aspettativa per PHPUnit e Mockery. Iniziamo con il primo.

Alcune distribuzioni Linux facilitano l'installazione di Mockery.

Noi usiamo il si aspetta () metodo per definire un'aspettativa da chiamare someMethod () una volta. Ma per far funzionare correttamente PHPUnit, noi dovere definire una classe chiamata AClassToBeMocked, e deve avere un someMethod () metodo.

Questo è un problema. Se stai prendendo in giro molti oggetti e sviluppi usando i principi TDD per un progetto top-down, non vorrai creare tutte le classi e i metodi prima del test. Il tuo test dovrebbe fallire per il giusto motivo, che il metodo previsto non è stato chiamato, invece di un errore critico di PHP senza alcuna relazione con l'implementazione reale. Vai avanti e prova a rimuovere il someMethod () definizione da AClassToBeMocked, e vedi cosa succede.

Mockery, d'altra parte, ti permette di definire mock per classi che non esistono.

Si noti che l'esempio precedente crea una simulazione per AnInexistentClass, che come suggerisce il nome, non esiste (né lo è someMethod () metodo).

Alla fine dell'esempio sopra, definiamo il SomeClass classe per esercitare il nostro codice. Inizializziamo un oggetto, chiamato $ someObject nella prima riga del metodo di test, ed esercitiamo efficacemente il codice dopo aver definito le nostre aspettative.

Notare che: La presa in giro valuta le sue aspettative vicino() metodo. Per questo motivo, dovresti sempre avere un demolire() metodo sul test che chiama \ Mockery :: close (). Altrimenti, Mockery dà falsi positivi.

Aspettatevi più di una chiamata

Come ho notato in precedenza, la maggior parte dei framework di derisione ha la capacità di specificare le aspettative per più chiamate di metodo. PHPUnit utilizza il $ This-> esattamente () costruire per questo scopo. Il seguente codice definisce le aspettative per chiamare più volte un metodo:

function testExpectMultiple () $ someObject = new SomeClass (); // Con PHPUnit 2 volte $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> si aspetta ($ this-> esattamente (2)) -> Metodo ( 'someMethod'); // Esercizio per PHPUnit $ someObject-> doSomething ($ phpunitMock); $ SomeObject-> doSomething ($ phpunitMock); // Con Mockery 2 volte $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ MockeryMock-> shouldReceive ( 'someMethod') -> due volte (); // Esercizio per Mockery $ someObject-> doSomething ($ mockeryMock); $ SomeObject-> doSomething ($ mockeryMock); // Con Mockery 3 volte $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ MockeryMock-> shouldReceive ( 'SomeMethod') -> volte (3); // Esercizio per Mockery $ someObject-> doSomething ($ mockeryMock); $ SomeObject-> doSomething ($ mockeryMock); $ SomeObject-> doSomething ($ mockeryMock); 

La presa in giro fornisce due metodi diversi per soddisfare meglio le tue esigenze. Il primo metodo, due volte(), si aspetta due chiamate al metodo. L'altro metodo è volte(), che ti consente di specificare un importo. L'approccio di Mockery è molto più pulito e facile da leggere.


Valori di ritorno

Un altro uso comune per i mock è testare il valore di ritorno di un metodo. Naturalmente, sia PHPUnit che Mockery hanno i mezzi per verificare i valori di ritorno. Ancora una volta, iniziamo con qualcosa di semplice.

Valori di ritorno semplici

Il seguente codice contiene sia il codice PHPUnit che Mockery. Ho anche aggiornato SomeClass per fornire un valore di ritorno verificabile.

class MockeryVersusPHPUnitGetMockTest estende PHPUnit_Framework_TestCase funzione protetta tearDown () \ Mockery :: close ();  // [...] // function testSimpleReturnValue () $ someObject = new SomeClass (); $ someValue = 'qualche valore'; // Con PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> si aspetta ($ this-> una volta ()) -> Metodo ( 'someMethod') -> volontà ($ this-> returnValue ($ someValue)); // Aspettatevi il valore restituito $ this-> assertEquals ($ someValue, $ someObject-> doSomething ($ phpunitMock)); // Con Mockery $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ MockeryMock-> shouldReceive ( 'someMethod') -> una volta () -> andReturn ($ someValue); // Aspettatevi il valore restituito $ this-> assertEquals ($ someValue, $ someObject-> doSomething ($ mockeryMock));  class AClassToBeMocked function someMethod ()  class SomeClass function doSomething ($ anotherObject) return $ anotherObject-> someMethod (); 

Entrambe le API di PHPUnit e Mockery sono semplici e facili da usare, ma trovo che Mockery sia più pulito e più leggibile.

Restituzione di valori diversi

I testatori di unità frequenti possono testimoniare le complicazioni con metodi che restituiscono valori diversi. Sfortunatamente, PHPUnit è limitato $ This-> a ($ indice) il metodo è il solo modo per restituire valori diversi dallo stesso metodo. Il seguente codice dimostra il a() metodo:

function testDemonstratePHPUnitCallIndexing () $ someObject = new SomeClass (); $ firstValue = 'primo valore'; $ secondValue = 'secondo valore'; // Con PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> si aspetta ($ this-> a (0)) -> Metodo ( 'someMethod') -> volontà ($ this-> returnValue ($ FirstValue)); $ PhpunitMock-> si aspetta ($ this-> a (1)) -> Metodo ( 'someMethod') -> volontà ($ this-> returnValue ($ SecondValue)); // Si aspetta il valore restituito $ this-> assertEquals ($ firstValue, $ someObject-> doSomething ($ phpunitMock)); $ this-> assertEquals ($ secondValue, $ someObject-> doSomething ($ phpunitMock)); 

Questo codice definisce due aspettative distinte e fa due chiamate diverse a someMethod (); così, questo test passa. Ma introduciamo una svolta e aggiungiamo una doppia chiamata nella classe testata:

 // [...] // function testDemonstratePHPUnitCallIndexingOnTheSameClass () $ someObject = new SomeClass (); $ firstValue = 'primo valore'; $ secondValue = 'secondo valore'; // Con PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> si aspetta ($ this-> a (0)) -> Metodo ( 'someMethod') -> volontà ($ this-> returnValue ($ FirstValue)); $ PhpunitMock-> si aspetta ($ this-> a (1)) -> Metodo ( 'someMethod') -> volontà ($ this-> returnValue ($ SecondValue)); // Aspettatevi il valore restituito $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ phpunitMock));  class SomeClass function doSomething ($ anotherObject) return $ anotherObject-> someMethod ();  function concatenate ($ anotherObject) return $ anotherObject-> someMethod (). ". $ anotherObject-> someMethod ();

Il test passa ancora. PHPUnit si aspetta due chiamate a someMethod () ciò accade all'interno della classe testata quando si esegue la concatenazione tramite il concatenare() metodo. La prima chiamata restituisce il primo valore e la seconda chiamata restituisce il secondo valore. Ma ecco il trucco: cosa succederebbe se raddoppi l'asserzione? Ecco il codice:

function testDemonstratePHPUnitCallIndexingOnTheSameClass () $ someObject = new SomeClass (); $ firstValue = 'primo valore'; $ secondValue = 'secondo valore'; // Con PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> si aspetta ($ this-> a (0)) -> Metodo ( 'someMethod') -> volontà ($ this-> returnValue ($ FirstValue)); $ PhpunitMock-> si aspetta ($ this-> a (1)) -> Metodo ( 'someMethod') -> volontà ($ this-> returnValue ($ SecondValue)); // Aspettatevi il valore restituito $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ phpunitMock)); $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ phpunitMock)); 

Restituisce il seguente errore:

Impossibile affermare che due stringhe siano uguali. --- Previsto +++ Attuale @@ @@ -'primo valore secondo valore '+'

PHPUnit continua a contare tra chiamate distinte a concatenare(). Nel momento in cui si verifica la seconda chiamata nell'ultima affermazione, Indice $ è ai valori 2 e 3. Puoi passare il test modificando le tue aspettative per considerare i due nuovi passaggi, come questo:

function testDemonstratePHPUnitCallIndexingOnTheSameClass () $ someObject = new SomeClass (); $ firstValue = 'primo valore'; $ secondValue = 'secondo valore'; // Con PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> si aspetta ($ this-> a (0)) -> Metodo ( 'someMethod') -> volontà ($ this-> returnValue ($ FirstValue)); $ PhpunitMock-> si aspetta ($ this-> a (1)) -> Metodo ( 'someMethod') -> volontà ($ this-> returnValue ($ SecondValue)); $ PhpunitMock-> si aspetta ($ this-> a (2)) -> Metodo ( 'someMethod') -> volontà ($ this-> returnValue ($ FirstValue)); $ PhpunitMock-> si aspetta ($ this-> a (3)) -> Metodo ( 'someMethod') -> volontà ($ this-> returnValue ($ SecondValue)); // Aspettatevi il valore restituito $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ phpunitMock)); $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ phpunitMock)); 

Probabilmente puoi vivere con questo codice, ma Mockery rende banale questo scenario. Non mi credi? Guarda:

function testMultipleReturnValuesWithMockery () $ someObject = new SomeClass (); $ firstValue = 'primo valore'; $ secondValue = 'secondo valore'; // Con Mockery $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ mockeryMock-> shouldReceive ('someMethod') -> andReturn ($ firstValue, $ secondValue, $ firstValue, $ secondValue); // Si aspetta il valore restituito $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ mockeryMock)); $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ mockeryMock)); 

Come PHPUnit, Mockery usa il conteggio degli indici, ma non dobbiamo preoccuparci degli indici. Invece, elenciamo semplicemente tutti i valori previsti e Mockery li restituisce in ordine.

Inoltre, restituisce PHPUnit NULLO per indici non specificati, ma Mockery restituisce sempre l'ultimo valore specificato. Questo è un bel tocco.

Prova più metodi con l'indicizzazione

Introduciamo un secondo metodo nel nostro codice, il concatWithMinus () metodo:

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

Questo metodo si comporta in modo simile a concatenare(), ma concatena i valori stringa con " - "a differenza di un singolo spazio. Poiché questi due metodi eseguono attività simili, ha senso testarli all'interno dello stesso metodo di test per evitare test duplicati.

Come dimostrato nel codice precedente, la seconda funzione utilizza un metodo diverso chiamato "mocked" anotherMethod (). Ho fatto questo cambiamento per costringerci a prendere in giro entrambi i metodi nei nostri test. La nostra classe mockable ora si presenta così:

class AClassToBeMocked function someMethod ()  function anotherMethod () 

Testare questo con PHPUnit potrebbe apparire come il seguente:

function testPHPUnitIndexingOnMultipleMethods () $ someObject = new SomeClass (); $ firstValue = 'primo valore'; $ secondValue = 'secondo valore'; // Con PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); // Prima e seconda chiamata su semeMethod: $ phpunitMock-> expects ($ this-> at (0)) -> method ('someMethod') -> will ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> si aspetta ($ this-> a (1)) -> Metodo ( 'someMethod') -> volontà ($ this-> returnValue ($ SecondValue)); // Aspettatevi il valore restituito $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ phpunitMock)); // Prima e seconda chiamata su anotherMethod: $ phpunitMock-> expects ($ this-> at (0)) -> method ('anotherMethod') -> will ($ this-> returnValue ($ firstValue)); $ PhpunitMock-> si aspetta ($ this-> a (1)) -> Metodo ( 'anotherMethod') -> volontà ($ this-> returnValue ($ SecondValue)); // Si aspetta il valore restituito $ this-> assertEquals ('primo valore - secondo valore', $ someObject-> concatWithMinus ($ phpunitMock)); 

La logica è sana. Definire due diverse aspettative per ciascun metodo e specificare il valore di ritorno. Funziona solo con PHPUnit 3.6 o successivi.

Notare che: PHPunit 3.5 e precedenti avevano un bug che non ripristinava l'indice per ciascun metodo, risultando in valori di ritorno imprevisti per i metodi falsificati.

Diamo un'occhiata allo stesso scenario con Mockery. Ancora una volta, otteniamo un codice molto più pulito. Vedi di persona:

function testMultipleReturnValuesForDifferentFunctionsWithMockery () $ someObject = new SomeClass (); $ firstValue = 'primo valore'; $ secondValue = 'secondo valore'; // Con Mockery $ mockeryMock = \ Mockery :: mock ('AnInexistentClass'); $ mockeryMock-> shouldReceive ('someMethod') -> andReturn ($ firstValue, $ secondValue); $ mockeryMock-> shouldReceive ('anotherMethod') -> andReturn ($ firstValue, $ secondValue); // Si aspetta il valore restituito $ this-> assertEquals ('first value second value', $ someObject-> concatenate ($ mockeryMock)); $ this-> assertEquals ('primo valore - secondo valore', $ someObject-> concatWithMinus ($ mockeryMock)); 

Valori di ritorno in base al parametro specificato

Onestamente, questo è qualcosa che PHPUnit semplicemente non può fare. Al momento della stesura di questo documento, PHPUnit non consente di restituire valori diversi dalla stessa funzione in base al parametro della funzione. Pertanto, il test seguente non riesce:

 // [...] // function testPHUnitCandDecideByParameter () $ someObject = new SomeClass (); // Con PHPUnit $ phpunitMock = $ this-> getMock ('AClassToBeMocked'); $ PhpunitMock-> si aspetta ($ this-> qualsiasi ()) -> Metodo ( 'ControllaNumero') -> con (2) -> volontà ($ this-> returnValue (2)); $ PhpunitMock-> si aspetta ($ this-> qualsiasi ()) -> Metodo ( 'ControllaNumero') -> con (3) -> volontà ($ this-> returnValue (3)); $ this-> assertEquals (4, $ someObject-> doubleNumber ($ phpunitMock, 2)); $ this-> assertEquals (6, $ someObject-> doubleNumber ($ phpunitMock, 3));  class AClassToBeMocked // [...] // function getNumber ($ number) return $ number;  classe SomeClass // [...] // function doubleNumber ($ anotherObject, $ number) return $ anotherObject-> getNumber ($ number) * 2; 

Si prega di ignorare il fatto che non vi è alcuna logica in questo esempio; fallirebbe anche se fosse presente. Questo codice, tuttavia, aiuta a illustrare l'idea.

Questo test fallisce perché PHPUnit non può distinguere tra le due aspettative nel test. La seconda aspettativa, in attesa di parametro 3, semplicemente sovrascrive il primo parametro in attesa 2. Se si tenta di eseguire questo test, si ottiene il seguente errore:

L'aspettativa non è riuscita per il nome del metodo è uguale a  quando invocato zero o più volte, il parametro 0 per l'invocazione AClassToBeMocked :: getNumber (2) non corrisponde al valore previsto. Non è stato possibile affermare che 2 corrispondenze previste 3.

Mockery può farlo e il codice qui sotto funziona esattamente come ci si aspetterebbe che funzioni. Il metodo restituisce valori diversi in base ai parametri forniti:

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

Misteri parziali

A volte, vuoi prendere in giro solo metodi specifici sul tuo oggetto (al contrario di prendere in giro un intero oggetto). Il seguente Calcolatrice la classe esiste già; vogliamo solo prendere in giro alcuni metodi:

class Calculator function add ($ firstNo, $ secondNo) return $ firstNo + $ secondNo;  funzione sottrarre ($ firstNo, $ secondNo) return $ firstNo - $ secondNo;  function multiply ($ value, $ multiplier) $ newValue = 0; for ($ i = 0; $ i<$multiplier;$i++) $newValue = $this->add ($ newValue, $ value); return $ newValue; 

Questo Calcolatrice la classe ha tre metodi: Inserisci(), sottrarre(), e moltiplicare(). Moltiplica utilizza un ciclo per eseguire la moltiplicazione chiamando il Inserisci() per un numero di volte specificato (ad es. 2 x 3 è veramente 2 + 2 + 2).

Supponiamo che vogliamo testare moltiplicare() in totale isolamento; quindi, ci prenderemo in giro Inserisci() e controllare per comportamento specifico su moltiplicare(). Ecco alcuni test possibili:

function testPartialMocking () $ value = 3; $ moltiplicatore = 2; $ risultato = 6; // PHPUnit $ phpMock = $ this-> getMock ('Calculator', array ('add')); $ PhpMock-> si aspetta ($ this-> esattamente (2)) -> Metodo ( 'add') -> volontà ($ this-> returnValue ($ risultato)); $ this-> assertEquals ($ result, $ phpMock-> multiply ($ value, $ moltiplicatore)); // Mockery $ mockeryMock = \ Mockery :: mock (new Calculator); $ MockeryMock-> shouldReceive ( 'add') -> andReturn ($ result); $ this-> assertEquals ($ result, $ mockeryMock-> multiply ($ value, $ multiplier)); // Parametri di controllo test esteso di mockery $ mockeryMock2 = \ Mockery :: mock (new Calculator); $ MockeryMock2-> shouldReceive ( 'add') -> con (0,3) -> andReturn (3); $ MockeryMock2-> shouldReceive ( 'add') -> con (3,3) -> andReturn (6); $ this-> assertEquals ($ result, $ mockeryMock2-> multiply ($ value, $ moltiplicatore)); 

Schermaglia offre ... un modo molto naturale per esprimere aspettative derisposte.

Il primo test PHPUnit è anemico; semplicemente verifica il metodo Inserisci() viene chiamato due volte e restituisce il valore finale di ogni chiamata. Ottiene il lavoro fatto, ma è anche un po 'complicato. PHPUnit ti obbliga a passare l'elenco di metodi a cui vuoi prendere in giro il secondo parametro $ This-> getMock (). Altrimenti, PHPUnit deriderebbe tutti i metodi, ognuno dei quali ritornerebbe NULLO per impostazione predefinita. Questa lista dovere essere mantenuto in accordo con le aspettative che definisci sul tuo oggetto deriso.

Ad esempio, se aggiungo una seconda aspettativa a $ phpMock'S sottrarre () metodo, PHPUnit lo ignorerebbe e chiamerebbe l'originale sottrarre () metodo. Cioè, a meno che non specifichi esplicitamente il nome del metodo (sottrarre) nel $ This-> getmock () dichiarazione.

Ovviamente, Mockery è diverso consentendo di fornire un vero oggetto \ Mockery :: finta (), e crea automaticamente una simulazione parziale. Ciò si ottiene implementando una soluzione proxy per il mocking. Tutte le aspettative definite vengono utilizzate, ma Mockery ricade sul metodo originale se non si specifica un'aspettativa per tale metodo.

Notare che: L'approccio di Mockery è molto semplice, ma le chiamate al metodo interno non passano attraverso l'oggetto deriso.

Questo esempio è fuorviante, ma illustra come non usare Le schifezze parziali di Mockery. Sì, Mockery crea una simulazione parziale se passi un oggetto reale, ma si prende gioco di lui solo chiamate esterne. Ad esempio, in base al codice precedente, il moltiplicare() il metodo chiama il reale Inserisci() metodo. Vai avanti e prova a cambiare l'ultima aspettativa da ... -> e Ripristina (6) a ... -> e Ripristina (7). Il test dovrebbe ovviamente fallire, ma non perché il reale Inserisci() Esegue invece il deriso Inserisci() metodo.

Ma possiamo aggirare questo problema creando mock come questo:

// Invece di $ mockeryMock = \ Mockery :: mock (nuovo Calcolatrice); // Crea il mock come questo $ mockeryMock = \ Mockery :: mock ('Calcolatrice [aggiungi]');

Sebbene sintatticamente diverso, il concetto è simile all'approccio di PHPUnit: devi elencare i metodi derisi in due punti. Ma per qualsiasi altro test, puoi semplicemente passare l'oggetto reale, che è molto più semplice, specialmente quando si ha a che fare con i parametri del costruttore.


Trattare con i parametri del costruttore

Aggiungiamo un costruttore con due parametri al Calcolatrice classe. Il codice revisionato:

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

Ogni test in questo articolo avrà esito negativo dopo l'aggiunta di questo costruttore. Più precisamente, il testPartialMock () i risultati del test nel seguente errore:

Argomento mancante 1 per Calculator :: __ construct (), chiamato in /usr/share/php/PHPUnit/Framework/MockObject/Generator.php sulla riga 224 e definito

PHPUnit tenta di prendere in giro l'oggetto reale chiamando automaticamente il costruttore, aspettandosi di avere i parametri impostati correttamente. Ci sono due modi per aggirare questo problema: o impostare i parametri, o non chiamare il costruttore.

// Specifica i parametri del costruttore $ phpMock = $ this-> getMock ('Calculator', array ('add'), array (1,2)); // Non chiamare il costruttore originale $ phpMock = $ this-> getMock ('Calculator', array ('add'), array (), ", false);

Presa in giro automagicamente funziona attorno a questo problema Va bene non specificare un parametro costruttore; Mockery semplicemente non chiamerà il costruttore. Ma è possibile specificare un elenco di parametri del costruttore da usare su Mockery. Per esempio:

function testMockeryConstructorParameters () $ result = 6; // Mockery // Non chiamare il costruttore $ noConstrucCall = \ Mockery :: mock ('Calculator [aggiungi]'); $ NoConstrucCall-> shouldReceive ( 'add') -> andReturn ($ result); // Usa i parametri del costruttore $ withConstructParams = \ Mockery :: mock ('Calculator [add]', array (1,2)); $ WithConstructParams-> shouldReceive ( 'add') -> andReturn ($ result); // Oggetto reale dell'utente con valori reali e simulazione su di esso $ realCalculator = new Calculator (1,2); $ mockRealObj = \ Mockery :: mock ($ realCalculator); $ MockRealObj-> shouldReceive ( 'add') -> andReturn ($ result); 

Considerazioni tecniche

Mockery è un'altra libreria che integra i tuoi test e potresti prendere in considerazione le implicazioni tecniche che questo può avere.

  • Mockery usa molta memoria. Dovrai aumentare la memoria massima a 512 MB se vuoi eseguire molti test (ad esempio oltre 1000 test con oltre 3000 asserzioni). Vedere php.ini documentazione per ulteriori dettagli.
  • È necessario organizzare i test per l'esecuzione in processi separati, quando si prendono in giro metodi statici e chiamate di metodi statici.
  • Puoi caricare automaticamente Mockery in ogni test usando la funzionalità bootstrap di PHPUnit (utile quando hai molti test e non vuoi ripetere te stesso).
  • È possibile automatizzare la chiamata a \ Mockery :: close () in ogni test demolire() modificando phpunit.xml.

Conclusioni finali

PHPUnit ha certamente i suoi problemi, specialmente quando si tratta di funzionalità ed espressività. La presa in giro può migliorare notevolmente la tua esperienza di derisione rendendo i tuoi test facili da scrivere e capire, ma non è perfetto (non esiste una cosa simile!).

Questo tutorial ha evidenziato molti aspetti chiave di Mockery, ma, onestamente, abbiamo appena scalfito la superficie. Assicurati di esplorare il repository Github del progetto per saperne di più.

Grazie per aver letto!