2012-06-29 1 views
5

Je travaille sur la mise en place d'une suite de tests pour un projet PHP Propel en utilisant Phactory, et PHPUnit. J'essaie actuellement de tester une unité qui fait une demande externe, et je veux boucher dans une réponse pour cette demande.Comment puis-je simuler une requête web externe dans PHPUnit?

Voici un extrait de la classe que je suis en train de tester:

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); 
    } 
    ... 

Et voici la fonction de test que je suis en train d'écrire.

// 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); 
} 

Je peux voir maintenant, après avoir essayé mon code de test, que je suis en train de créer un objet fantaisie avec getMock, puis ne jamais l'utiliser. Donc la fonction fetchUrl s'exécute réellement, ce que je ne veux pas. Mais je veux toujours être en mesure d'utiliser l'objet Phactory créé endpoint, car il a tous les champs remplis à partir de ma définition d'usine.

Y a-t-il un moyen pour moi de boucher une méthode sur un objet existant? Donc, je pourrais talonner fetch_url sur l'objet Endpoint $endpoint que je viens de créer? Ou est-ce que je vais à ce sujet tout faux; Y a-t-il une meilleure façon pour moi de tester mon unité mes fonctions qui reposent sur des requêtes web externes? J'ai lu la documentation de PHPUnit concernant "Stubbing and Mocking Web Services", mais leur exemple de code pour le faire est de 40 lignes, sans avoir à définir votre propre wsdl. J'ai de la difficulté à croire que c'est la façon la plus commode pour moi de gérer cela, à moins que les bonnes gens de SO ne le ressentent autrement.

Appréciez grandement toute aide, j'ai été raccroché à ce sujet toute la journée. Merci!!

Répondre

11

Du point de vue de l'essai, votre code a deux problèmes:

  1. L'URL est hardcoded, vous laissant aucun moyen de modifier pour le développement, les tests ou la production
  2. Le point final sait comment récupérer des données . À partir de votre code, je ne peux pas dire ce que le point de terminaison fait vraiment, mais si ce n'est pas un objet de bas niveau, "Just get me Data", il ne devrait pas savoir comment récupérer les données.

Avec votre code comme celui-ci, il n'y a pas de bonne façon de tester votre code. Vous pourriez travailler avec Reflections, changer votre code et ainsi de suite. Le problème avec cette approche est que vous ne testez pas votre objet réel mais une réflexion qui a changé pour fonctionner avec le test.

Si vous voulez écrire « bons » tests, votre point final devrait ressembler à ceci:

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); 
     // ... 
    } 
} 

Maintenant, vous pouvez injecter une maquette de la DataParser qui retourne une réponse par défaut en fonction de ce que vous voulez tester .

La question suivante pourrait être: Comment tester le DataParser? La plupart du temps, vous ne le faites pas. Si c'est juste un wrapper autour des fonctions standard php, vous n'avez pas besoin.Votre DataParser devrait vraiment être très faible niveau, qui ressemble à ceci:

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

Si vous avez besoin ou si vous voulez le tester, vous pouvez créer un Webservice qui vit au sein de vos tests et agit comme un « Mock », revenant toujours préconfiguré Les données. Vous pourriez alors appeler cette URL fictive au lieu du vrai et évaluer le retour.

+2

C'est ce que j'ai fini par faire, même si je n'en suis pas ravi. Une classe supplémentaire juste pour emballer 'file_get_contents' _feels_ comme exagéré pour moi. C'est un changement de code que je ferais seulement pour le tester, et ça me fait mal. Je viens de ruby, qui avait la gemme [webmock] (https://github.com/bblimke/webmock/), qui fait exactement ce que vous avez décrit dans votre dernier paragraphe: "crée un Webservice qui vit dans vos tests et agit comme un "Mock", renvoyant toujours des données préconfigurées ". Au lieu de coder moi-même je vais aller la route de l'objet maquette pour l'instant. Merci pour les pensées! – goggin13

+2

Je ne pense pas que ce soit les frais généraux. Pensez-y comme ceci: Vous allez utiliser ce code dans de nombreux endroits. Dans un an, il y a une meilleure implémentation pour file_get_contents et vous devez le changer partout. Ou pensez que vous voulez passer à Buzz (que je recommanderais). Dans le code ci-dessus, vous injectez l'objet et vous devez modifier un appel de méthode. Peut-être que c'est un peu léger dans votre petit exemple, mais au fur et à mesure que votre application s'agrandit et que vous souhaiterez réutiliser file_get_contents, vous l'apprécierez peut-être. – Sgoettschkes

Questions connexes