Primero, donde está mi conocimiento:
Las pruebas unitarias son aquellas que prueban una pequeña parte de código (métodos únicos, en su mayoría).
Las pruebas de integración son aquellas que prueban la interacción entre múltiples áreas de código (que con suerte ya tienen sus propias pruebas unitarias). A veces, partes del código bajo prueba requieren que otro código actúe de una manera particular. Aquí es donde entran Mocks & Stubs. Por lo tanto, simulamos / eliminamos una parte del código para que funcione de manera muy específica. Esto permite que nuestra prueba de integración se ejecute de manera predecible sin efectos secundarios.
Todas las pruebas deben poder ejecutarse de forma independiente sin compartir datos. Si es necesario compartir datos, esto es una señal de que el sistema no está lo suficientemente desacoplado.
A continuación, la situación a la que me enfrento:
Al interactuar con una API externa (específicamente, una API RESTful que modificará los datos en vivo con una solicitud POST), entiendo que podemos (¿deberíamos?) Simular la interacción con esa API (que se indica con más elocuencia en esta respuesta ) para una prueba de integración . También entiendo que podemos realizar una prueba unitaria de los componentes individuales de la interacción con esa API (construir la solicitud, analizar el resultado, generar errores, etc.). Lo que no entiendo es cómo hacer esto.
Entonces, finalmente: Mi pregunta (s).
¿Cómo pruebo mi interacción con una API externa que tiene efectos secundarios?
Un ejemplo perfecto es la API de contenido de Google para compras . Para poder realizar la tarea en cuestión, se requiere una cantidad decente de trabajo de preparación, luego realizar la solicitud real y luego analizar el valor de retorno. Parte de esto es sin ningún entorno de "caja de arena" .
El código para hacer esto generalmente tiene bastantes capas de abstracción, algo como:
<?php
class Request
{
public function setUrl(..){ /* ... */ }
public function setData(..){ /* ... */ }
public function setHeaders(..){ /* ... */ }
public function execute(..){
// Do some CURL request or some-such
}
public function wasSuccessful(){
// some test to see if the CURL request was successful
}
}
class GoogleAPIRequest
{
private $request;
abstract protected function getUrl();
abstract protected function getData();
public function __construct() {
$this->request = new Request();
$this->request->setUrl($this->getUrl());
$this->request->setData($this->getData());
$this->request->setHeaders($this->getHeaders());
}
public function doRequest() {
$this->request->execute();
}
public function wasSuccessful() {
return ($this->request->wasSuccessful() && $this->parseResult());
}
private function parseResult() {
// return false when result can't be parsed
}
protected function getHeaders() {
// return some GoogleAPI specific headers
}
}
class CreateSubAccountRequest extends GoogleAPIRequest
{
private $dataObject;
public function __construct($dataObject) {
parent::__construct();
$this->dataObject = $dataObject;
}
protected function getUrl() {
return "http://...";
}
protected function getData() {
return $this->dataObject->getSomeValue();
}
}
class aTest
{
public function testTheRequest() {
$dataObject = getSomeDataObject(..);
$request = new CreateSubAccountRequest($dataObject);
$request->doRequest();
$this->assertTrue($request->wasSuccessful());
}
}
?>
Nota: Este es un ejemplo de PHP5 / PHPUnit
Dado que testTheRequest
es el método llamado por la suite de pruebas, el ejemplo ejecutará una solicitud en vivo.
Ahora, esta solicitud en vivo (con suerte, siempre que todo haya ido bien) realizará una solicitud POST que tiene el efecto secundario de alterar los datos en vivo.
¿Es esto aceptable? ¿Qué alternativas tengo? No veo una forma de simular el objeto Solicitud para la prueba. E incluso si lo hiciera, significaría configurar resultados / puntos de entrada para cada ruta de código posible que acepte la API de Google (que en este caso tendría que encontrarse por prueba y error), pero me permitiría el uso de accesorios.
Una extensión adicional es cuando ciertas solicitudes dependen de que ciertos datos ya estén en vivo. Utilizando la API de contenido de Google como ejemplo nuevamente, para agregar una fuente de datos a una subcuenta, la subcuenta ya debe existir.
Un enfoque en el que puedo pensar son los siguientes pasos;
- En
testCreateAccount
- Crea una subcuenta
- Confirmar que se creó la subcuenta
- Eliminar la subcuenta
- Tenga
testCreateDataFeed
en dependtestCreateAccount
no tener ningún error- En
testCreateDataFeed
, crea una nueva cuenta - Crea el feed de datos
- Confirmar que se creó el feed de datos
- Eliminar la fuente de datos
- Eliminar la subcuenta
- En
Esto entonces plantea la pregunta adicional; ¿Cómo pruebo la eliminación de cuentas / feeds de datos? testCreateDataFeed
me parece sucio - ¿Qué pasa si falla la creación de la fuente de datos? La prueba falla, por lo tanto, la subcuenta nunca se elimina ... No puedo probar la eliminación sin creación, así que escribo otra prueba ( testDeleteAccount
) en la que se basa testCreateAccount
antes de crear y luego eliminar una cuenta propia (ya que los datos no deberían compartirse entre pruebas).
En resumen
- ¿Cómo pruebo la interacción con una API externa que afecta a los datos en vivo?
- ¿Cómo puedo simular / apuntar objetos en una prueba de integración cuando están ocultos detrás de capas de abstracción?
- ¿Qué hago cuando falla una prueba y los datos en vivo se dejan en un estado inconsistente?
- ¿Cómo en el código realmente hago todo esto?
Relacionado: