2012-06-29 9 views
5

Sto lavorando sulla creazione di una suite di test per un PHP Propel progetto utilizzando Phactory e PHPUnit. Attualmente sto provando a testare una funzione che fa una richiesta esterna, e voglio stubare una risposta finta per quella richiesta.Come posso prendere in giro una richiesta Web esterna in PHPUnit?

Ecco un frammento della classe che sto cercando di prova:

class Endpoint { 
    ... 
    public function parseThirdPartyResponse() { 
    $response = $this->fetchUrl("www.example.com/api.xml"); 
    // do stuff and return 
    ... 
    } 

    public function fetchUrl($url) { 
    return file_get_contents($url); 
    } 
    ... 

Ed ecco la funzione di test che sto cercando di scrivere.

// my factory, defined in a seperate file 
Phactory::define('endpoint', array('identifier' => 'endpoint_$n'); 

// a test case in my endpoint_test file 
public function testParseThirdPartyResponse() { 
    $phEndpoint = Phactory::create('endpoint', $options); 
    $endpoint = new EndpointQuery()::create()->findPK($phEndpoint->id); 

    $stub = $this->getMock('Endpoint'); 
    $xml = "...<target>test_target</target>..."; // sample response from third party api 

    $stub->expects($this->any()) 
     ->method('fetchUrl') 
     ->will($this->returnValue($xml)); 

    $result = $endpoint->parseThirdPartyResponse(); 
    $this->assertEquals('test_target', $result); 
} 

posso vedere ora, dopo che ho provato il mio codice di prova, che sto creando un oggetto fittizio con getMock, e poi non utilizzarlo. Quindi la funzione fetchUrl viene effettivamente eseguita, cosa che non voglio. Ma voglio comunque essere in grado di usare l'oggetto Phactory creato endpoint, poiché ha tutti i campi corretti popolati dalla mia definizione di fabbrica.

C'è un modo per me di stubare un metodo su un oggetto esistente? Così ho potuto mozzare fetch_url sull'oggetto $endpoint Oggetto Endpoint che ho appena creato?

O sto andando tutto sbagliato? c'è un modo migliore per testare l'unità le mie funzioni che si basano su richieste web esterne?

Ho letto la documentazione di PHPUnit riguardante "Stubbing and Mocking Web Services", ma il loro codice di esempio per farlo è lungo 40 righe, senza includere il dover definire il proprio wsdl. Ho difficoltà a credere che questo sia il modo più conveniente per me di gestirlo, a meno che le brave persone di SO non sentano il contrario.

Apprezzo molto qualsiasi aiuto, sono stato appeso su questo tutto il giorno. Grazie!!

risposta

11

Dal punto di vista di prova, il codice ha due problemi:

  1. L'url è hardcoded, lasciando alcun modo di alterare per sviluppo, test o la produzione
  2. L'endpoint sa su come recuperare i dati . Dal tuo codice non posso dire cosa fa realmente l'endpoint, ma se non è un oggetto di basso livello "Just get me Data", non dovrebbe sapere come recuperare i dati.

Con il codice come questo, non c'è un buon modo per testare il codice. Potresti lavorare con Reflections, cambiare il tuo codice e così via. Il problema con questo approccio è che non testate il vostro oggetto reale ma una riflessione che ha cambiato il lavoro con il test.

Se si vuole scrivere "buoni" esami, il punto finale dovrebbe essere simile a questa:

class Endpoint { 

    private $dataParser; 
    private $endpointUrl; 

    public function __construct($dataParser, $endpointUrl) { 
     $this->dataPartser = $dataParser; 
     $this->endpointUrl = $endpointUrl; 
    } 

    public function parseThirdPartyResponse() { 
     $response = $this->dataPartser->fetchUrl($this->endpointUrl); 
     // ... 
    } 
} 

Ora si può iniettare un Mock della DataParser che restituisce un po 'di risposta predefinita a seconda di ciò che si desidera testare .

La prossima domanda potrebbe essere: Come posso testare DataParser? Principalmente, non lo fai. Se è solo un wrapper attorno alle funzioni standard di php, non è necessario.Il tuo DataParser in realtà dovrebbe essere molto basso livello, cercando in questo modo:

class DataParser { 
    public function fetchUrl($url) { 
     return file_get_contents($url); 
    } 
} 

Se avete bisogno o volete provarlo, è possibile creare un servizio Web che vive all'interno del vostro test e agisce come un "Mock", sempre tornando preconfigurato dati. Potresti quindi chiamare questo URL simulato al posto di quello reale e valutare il ritorno.

+2

Questo è quello che ho finito per fare, anche se non ne sono entusiasta. Una classe addizionale solo per racchiudere 'file_get_contents' _feels_ come overkill per me. È un cambio di codice che farei solo per testarlo, e questo mi fa sentire. Vengo da ruby, che ha la gemma [webmock] (https://github.com/bblimke/webmock/), che fa proprio quello che hai descritto nel tuo ultimo paragrafo: "crea un servizio Web che vive all'interno dei tuoi test e funge da "Mock", restituendo sempre dati preconfigurati ". Invece di programmare che per ora userò il finto percorso degli oggetti. Grazie per i pensieri! – goggin13

+2

Non penso che sia un sovraccarico. Pensaci in questo modo: userai questo codice in molti posti. Tra un anno, c'è un modo migliore di implementare per file_get_contents e devi cambiarlo ovunque. O pensi di voler passare a Buzz (che consiglierei). Nel codice sopra, si inietta l'oggetto e si deve cambiare una chiamata di metodo. Forse è un sovraccarico nel tuo piccolo esempio, ma man mano che la tua applicazione si ingrandisce e potresti voler riutilizzare file_get_contents, potresti apprezzarlo. – Sgoettschkes

Problemi correlati