2012-06-29 23 views
5

Estoy trabajando en la configuración de un conjunto de pruebas para un proyecto de PHP Propel usando Phactory, y PHPUnit. Actualmente estoy intentando probar la unidad en una función que hace una solicitud externa, y quiero resguardarme en una respuesta falsa para esa solicitud.¿Cómo puedo simular una solicitud web externa en PHPUnit?

He aquí un fragmento de la clase que estoy tratando de prueba:

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

y aquí está la función de prueba que estoy tratando de escribir.

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

puedo ver ahora, después probé mi código de prueba, que estoy creando un objeto de burla con getMock, y luego no usarlo. Así que la función fetchUrl en realidad se ejecuta, lo que no quiero. Pero todavía quiero poder usar el objeto Phactory creado endpoint, ya que tiene todos los campos correctos poblados de mi definición de fábrica.

¿Hay alguna manera de que resida un método en un objeto existente? ¿Así que podría insertar fetch_url en el objeto de punto final $endpoint que acabo de crear?

¿O estoy haciendo todo mal? ¿Hay alguna manera mejor para que pruebe unitariamente mis funciones que dependen de solicitudes web externas?

He leído la documentación de PHPUnit con respecto a "Stubbing and Mocking Web Services", pero su código de muestra para hacerlo es de 40 líneas, sin incluir tener que definir tu propio wsdl. Me cuesta creer que esa es la forma más conveniente de manejar esto, a menos que las buenas personas de SO sientan lo contrario.

Agradezco muchísimo cualquier ayuda, he estado colgado de esto todo el día. ¡¡Gracias!!

Respuesta

11

Desde una perspectiva de pruebas, el código tiene dos problemas:

  1. La url es codificado, dejándole ninguna manera de alterarlo para el desarrollo, prueba o producción
  2. El punto final sabe acerca de cómo recuperar los datos . De su código no puedo decir lo que realmente hace el punto final, pero si no se trata de un objeto de bajo nivel "Solo cómprenme datos", no debería saber cómo recuperar los datos.

Con su código así, no hay una buena forma de probar el código. Podrías trabajar con Reflections, cambiar tu código, etc. El problema con este enfoque es que no prueba su objeto real sino una reflexión que tiene un cambio para trabajar con la prueba.

Si desea escribir pruebas "bueno", el punto final debe ser algo como esto:

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

Ahora usted podría inyectar un simulacro de la DataParser que devuelve algún tipo de respuesta por defecto en función de lo que desea probar .

La siguiente pregunta podría ser: ¿Cómo puedo probar el DataParser? Sobre todo, no lo haces. Si se trata solo de un envoltorio de las funciones estándar de php, no es necesario.Su DataParser realmente debe ser muy bajo nivel, con este aspecto:

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

Si necesita o quiere probarlo, puede crear un servicio web, que vive dentro de sus pruebas y actúa como un "simulacro", volviendo siempre preconfigurado datos. A continuación, podría llamar a esta url falsa en lugar de la real y evaluar el retorno.

+2

Esto es lo que terminé haciendo, aunque no estoy muy contento con él. Una clase adicional para ajustar 'file_get_contents' _feels_ overkill para mí. Es un cambio de código que haría solo para probarlo, y eso me resulta desagradable. Vengo de ruby, que tenía la gema [webmock] (https://github.com/bblimke/webmock/), que hace exactamente lo que describiste en el último párrafo: "crea un servicio web que vive dentro de tus pruebas y actúa como un "simulacro", siempre devolviendo datos preconfigurados ". En lugar de codificar yo mismo, iré por la ruta del objeto simulado por el momento. Gracias por los pensamientos! – goggin13

+2

No creo que sea sobrecargado. Piénselo de esta manera: va a utilizar este código en muchos lugares. En un año a partir de ahora, hay una forma mejor implementación para file_get_contents y debe cambiarlo en todas partes. O piense que quiere cambiar a Buzz (que recomendaría). En el código anterior, inyectas el objeto y tienes que cambiar una llamada a un método. Mayvbe está sobrecargado en su pequeño ejemplo, pero a medida que su aplicación aumenta de tamaño y es posible que desee reutilizar file_get_contents, puede apreciarlo. – Sgoettschkes

Cuestiones relacionadas