He probado mi clase de unidad, ahora ¿cómo empiezo con una prueba de integración?

19

He escrito una clase que administra destinatarios en una lista de MailChimp, llamada MailChimpRecipient. Utiliza la clase MCAPI, que es un contenedor de API de terceros.

http://apidocs.mailchimp.com/api/1.3/ http://apidocs.mailchimp.com/api/downloads/

Paso el objeto MCAPI al constructor del objeto MailChimpRecipient, así que escribí pruebas unitarias usando PHPUnit que prueban toda la lógica en mi propia clase (no estoy probando la clase MCAPI). Tengo una cobertura de código del 100% y todas las pruebas pasan. Esto se hace burlándose y tropezando con el objeto MCAPI.

Mi siguiente paso fue escribir una prueba de integración, también usando PHPUnit, donde construiría el accesorio MailChimpRecipient usando un objeto MCAPI real, configurado para usar una lista real de MailChimp.

He escrito lo que creo que es una prueba de integración, que básicamente ejecuta pruebas contra la interfaz pública del objeto, como:

public function testAddedRecipientCanBeFound()
{
    $emailAddress = '[email protected]';
    $forename = 'Fred';
    $surname = 'Smith';

    // First, delete the email address if it is already on the list
    $oldRecipient = $this->createRecipient();
    if($oldRecipient->find($emailAddress))
    {
        $oldRecipient->delete();
    }
    unset($oldRecipient);

    // Add the recipient using the test data
    $newRecipient = $this->createRecipient();
    $newRecipient->setForename($forename);
    $newRecipient->setSurname($surname);
    $newRecipient->setEmailAddress($emailAddress);
    $newRecipient->add();
    unset($newRecipient);

    // Assert that the recipient can be found using the same email address
    $this->assertTrue($this->_recipient->find($emailAddress));
}

La prueba de "integración" no prueba ninguno de los elementos internos de la clase, solo se asegura de que, dado un objeto MCAPI real, se comporte como se anuncia.

¿Es esto correcto? ¿Es esta la mejor manera de ejecutar una prueba de interposición? Después de todo, las partes internas han sido probadas con una prueba unitaria. ¿Estoy en lo cierto al pensar que la prueba de integración está ahí para probar que realmente funciona, de acuerdo con la forma en que se anuncia su comportamiento?

Para ir un paso más allá, la clase MailChimpRecipient implementa una interfaz, que también será implementada por otras clases. La idea es usar una fábrica para pasar diferentes tipos de objetos de destinatario de la lista de correo a mi código, que hacen lo mismo, aunque usan diferentes proveedores de listas de correo. Dado que mis pruebas de integración prueban esa interfaz, ¿qué tal si la usamos para todas las clases que implementan la interfaz? Luego, en el futuro, si diseño una nueva clase para ser utilizada de manera intercambiable, puedo ejecutar la misma prueba de integración antes de insertarla en un proyecto.

¿Suena esto razonable? Las pruebas unitarias prueban las partes internas de un objeto, las pruebas de integración aseguran que se comporte como se anuncia.

Lewis Bassett
fuente
44
Creo que tienes demasiada lógica en tu prueba. Ejecutas mucho código hasta que haces la afirmación. Probablemente desee probar primero la eliminación de un destinatario. Pero eso no responde a su pregunta, solo un comentario.
Hakre
1
Bueno, debe hacer uso de la setUpfunción para establecer los motivos para ejecutar sus pruebas. Si la entrada no está definida, bueno, realmente no puede probar. La entrada debe ser precisa, estricta y siempre la misma. Si no se cumple una condición previa de una prueba, omita la prueba. Luego, analice por qué se salta y si necesita agregar pruebas adicionales y / o setUpno se hace correctamente.
Hakre
1
Además, no codifique los valores de prueba dentro de una prueba propia, sino que haga que los miembros de la clase puedan compartirse entre las pruebas (y cambiarse en un lugar central) o usar DataProvider(esa es una función que ofrece entradas como parámetros para una prueba).
Hakre
1
Ingrese el significado de todo lo que opera su función de prueba. A medida que prueba agregar un destinatario y desea asegurarse de que ya no existe, al menos debe afirmar la eliminación en caso de que se active. De lo contrario, no se garantizará que la condición previa de su prueba sea comprobable.
Hakre
1
+1 para una buena pregunta, pero también votó para migrar a Programadores. Parece que ahí es donde pertenecen las preguntas sobre las estrategias de prueba
GordonM

Respuestas:

17

Al probar su código, debe prestar atención a tres áreas:

  • Prueba de escenario
  • Prueba funcional
  • Examen de la unidad

Normalmente, la cantidad de pruebas que tiene en cada categoría tendría la forma de una pirámide, es decir, muchas pruebas unitarias en la parte inferior, algunas pruebas funcionales en el medio y solo unas pocas pruebas de escenarios.

Con una prueba unitaria, se burla de todo lo que usa la clase bajo prueba y la prueba de forma aislada (es por eso que es importante asegurarse de que dentro de su clase recupere todas las dependencias a través de la inyección para que puedan reemplazarse bajo prueba).

Con las pruebas unitarias, usted prueba todas las posibilidades, por lo que no solo la 'ruta feliz' sino también todas las condiciones de error.

Si está completamente seguro de que todas sus unidades funcionan de manera aislada, escriba un par de pruebas (pruebas funcionales) para asegurarse de que las unidades también funcionen cuando se combinen. Luego, escribe una prueba de escenario, que prueba el cableado entre todos los módulos funcionales.

Por ejemplo, suponga que está probando un automóvil.

Puede ensamblar todo el automóvil y, como conductor, verificar todas las condiciones posibles, pero eso sería realmente difícil de hacer.

En su lugar, probaría una pequeña parte del motor con todas las posibilidades (prueba unitaria)

Luego prueba todo el motor (separado del automóvil), lo que sería una prueba funcional.

Como última prueba, ingresa su llave, enciende el automóvil y lo conduce al estacionamiento. Si eso funciona, entonces sabe que todas las partes (batería, combustible, motor, ...) están conectadas y, dado que las probó de forma aislada, puede estar bastante seguro de que todo el automóvil funciona correctamente.

Entonces, en su caso, probó todas las condiciones de error y el camino feliz en la prueba de su unidad y sabe que solo tiene que hacer una prueba de extremo a extremo con los 'componentes reales' para verificar si el cableado es correcto.

Algunos otros puntos,

  • Evite la lógica condicional en su prueba unitaria. Si tiene que limpiar, está utilizando algún tipo de estado global y las pruebas pueden influirse repentinamente entre sí.
  • No especifique ningún dato que no sea relevante para la prueba. Si cambiara el nombre o el apellido, ¿fallaría la prueba? Probablemente no porque sea la dirección de correo electrónico lo importante, sino porque lo mencionas explícitamente en tu prueba, no puedo estar seguro. Intente mirar el Patrón de construcción para construir sus datos de prueba y hacer explícito lo que es realmente importante.
Wouter de Kort
fuente
Gracias, eso confirma mucho de lo que pensaba. Solo para aclarar: esta NO es una prueba unitaria. Ya he escrito una prueba unitaria, que prueba el objeto en completo aislamiento y tiene una cobertura del código del 100% del objeto. Se suponía que era una prueba de integración, para asegurarse de que funciona cuando le inyecto un objeto MCAPI real. Solo necesito eliminar todos los destinatarios que se agreguen a la lista, eso es todo lo que es la limpieza, y se implementa para garantizar que ninguna de las pruebas influya entre sí. ¿Qué sugerirías en su lugar?
1
¡Si! Comprendí que ya hiciste las pruebas unitarias. ¿El objeto MCAPI realiza un seguimiento de los destinatarios y es esa la limpieza que tiene que hacer? Si es el 'problema' de terceros, no hay nada que pueda hacer al respecto en una prueba de integración. Si, por otro lado, realiza un seguimiento de la lista, debe asegurarse de evitar los datos globales (y los singletons) para asegurarse de que las pruebas no se influyan entre sí. En un mundo perfecto, limpiar las cosas cuando comienza / termina una prueba, apunta a un defecto de diseño, pero en el mundo real, no siempre se puede evitar.
Wouter de Kort
1
Agregaría que la prueba de escenarios probablemente no sea realmente algo para lo que PHPUnit sea adecuado. Es posible que desee ver alguna herramienta que pueda ejecutar en un navegador como Selenium, o una herramienta que pueda simular un navegador, como jMeter.
GordonM
¡Gracias chicos! Seguro que hay mucho que aprender cuando se trata de escribir un buen código comprobable, ¿no es así? Me ordené una copia de este libro: amazon.co.uk/… . Con suerte, lo que todos han dicho tendrá un poco más de sentido después de haber leído eso. @Wouter, solo estoy eliminando un destinatario porque la prueba habría provocado que se agregara una dirección de correo electrónico a la lista. Lo estoy eliminando para que la lista no se vea afectada por esa prueba.
1
@LewisBassett No soy desarrollador de Php, pero los patrones de prueba xUnit ( amazon.com/xUnit-Test-Patterns-Refactoring-Code/dp/0131495054 ) son definitivamente una buena lectura. También los artículos en misko.hevery.com/code-reviewers-guide son realmente interesantes.
Wouter de Kort