Determinar qué es una prueba unitaria útil

47

He estado revisando los documentos de phpunit y encontré la siguiente cita:

Siempre puedes escribir más pruebas. Sin embargo, encontrará rápidamente que solo una fracción de las pruebas que pueda imaginar son realmente útiles. Lo que desea es escribir pruebas que fallen aunque piense que deberían funcionar, o pruebas que tengan éxito aunque piense que deberían fallar. Otra forma de pensarlo es en términos de costo / beneficio. Desea escribir pruebas que le devolverán el dinero con información. --Erich Gamma

Me hizo preguntarme. ¿Cómo se determina qué hace que una prueba unitaria sea más útil que otra, aparte de lo que se indica en esa cita sobre costo / beneficio? ¿Cómo haces para decidir para qué parte de tu código creas pruebas unitarias? Lo pregunto porque otra de esas citas también dijo:

Entonces, si no se trata de pruebas, ¿de qué se trata? Se trata de averiguar qué estás tratando de hacer antes de salir corriendo a medias para intentar hacerlo. Usted escribe una especificación que define un pequeño aspecto del comportamiento de forma concisa, inequívoca y ejecutable. Es así de simple. ¿Eso significa que escribes pruebas? No. Significa que escribes especificaciones de lo que tu código tendrá que hacer. Significa que especifica el comportamiento de su código antes de tiempo. Pero no mucho antes de tiempo. De hecho, justo antes de escribir el código es mejor porque es cuando tiene tanta información a mano como la tendrá hasta ese momento. Al igual que TDD bien hecho, trabajas en pequeños incrementos ... especificando un pequeño aspecto del comportamiento a la vez, luego lo implementas. Cuando te das cuenta de que se trata de especificar el comportamiento y no escribir pruebas, su punto de vista cambia. De repente, la idea de tener una clase de prueba para cada una de sus clases de producción es ridículamente limitante. Y la idea de probar cada uno de sus métodos con su propio método de prueba (en una relación 1-1) será ridícula. --Dave Astels

La sección importante de eso es

* Y la idea de probar cada uno de sus métodos con su propio método de prueba (en una relación 1-1) será ridícula. * *

Entonces, si crear una prueba para cada método es 'risible', ¿cómo / cuándo elige para qué escribe las pruebas?

zcourts
fuente
55
Es una buena pregunta, pero creo que es una pregunta independiente del lenguaje de programación: ¿por qué la etiquetó php?
Aquí hay algunos artículos realmente buenos que me proporcionaron una buena dirección sobre qué pruebas unitarias debería escribir y para qué áreas es importante proporcionar pruebas automatizadas. - blog.stevensanderson.com/2009/11/04/… - blog.stevensanderson.com/2009/08/24/… - ayende.com/blog/4218/scenario-driven-tests
Kane

Respuestas:

27

¿Cuántas pruebas por método?

Bueno, el máximo teórico y poco práctico es la complejidad de N-Path (suponga que todas las pruebas cubren diferentes formas a través del código;)). ¡El mínimo es UNO! Según el método público , es decir, no prueba los detalles de implementación, solo los comportamientos externos de una clase (valores de retorno y llamadas a otros objetos).

Usted cita:

* Y la idea de probar cada uno de sus métodos con su propio método de prueba (en una relación 1-1) será ridícula. * *

y luego preguntar:

Entonces, si crear una prueba para cada método es 'risible', ¿cómo / cuándo elige para qué escribe las pruebas?

Pero creo que entendiste mal al autor aquí:

La idea de tener one test methodper one method in the class to testes lo que el autor llama "risible".

(Al menos para mí) No se trata de 'menos' sino de 'más'

Así que déjame reformular como lo entendí:

Y la idea de probar cada uno de sus métodos con SOLO UN MÉTODO (su propio método de prueba en una relación 1-1) será ridícula.

Para cotizar su cotización nuevamente:

Cuando te das cuenta de que se trata de especificar el comportamiento y no escribir pruebas, tu punto de vista cambia.


Cuando practicas TDD no piensas :

Tengo un método calculateX($a, $b);y necesita una prueba testCalculcateXque pruebe TODO sobre el método.

Lo que TDD le dice es que piense en lo que DEBE hacer su código :

Necesito calcular el mayor de dos valores (¡ primer caso de prueba! ) Pero si $ a es menor que cero, entonces debería producir un error (¡ segundo caso de prueba! ) Y si $ b es menor que cero, debería ... ( tercer caso de prueba! ) y así sucesivamente.


Desea probar comportamientos, no solo métodos únicos sin contexto.

De esa manera, obtienes un conjunto de pruebas que es documentación para tu código y REALMENTE explica lo que se espera que haga, tal vez incluso por qué :)


¿Cómo haces para decidir para qué parte de tu código creas pruebas unitarias?

Bueno, todo lo que termina en el repositorio o en cualquier lugar cerca de la producción necesita una prueba. No creo que el autor de sus citas esté en desacuerdo con eso, como intenté decir en lo anterior.

Si no tiene una prueba, es mucho más difícil (más costoso) cambiar el código, especialmente si no es usted quien realiza el cambio.

TDD es una forma de asegurarse de que tiene pruebas para TODO, pero siempre y cuando las escriba, está bien. Por lo general, escribirlos el mismo día ayuda, ya que no lo hará más tarde, ¿verdad? :)



Respuesta a comentarios:

No se puede probar una cantidad decente de métodos dentro de un contexto particular porque dependen o dependen de otros métodos

Bueno, hay tres cosas que esos métodos pueden llamar:

Métodos públicos de otras clases.

Podemos burlarnos de otras clases, por lo que hemos definido el estado allí. Tenemos el control del contexto para que no haya un problema allí.

* Métodos protegidos o privados en el mismo *

Cualquier cosa que no sea parte de la API pública de una clase no se prueba directamente, por lo general.

Desea probar el comportamiento y no la implementación, y si una clase hace todo, funciona en un gran método público o en muchos métodos protegidos más pequeños que se llaman es la implementación . Desea CAMBIAR esos métodos protegidos SIN tocar sus pruebas. ¡Porque sus pruebas se romperán si su código cambia, cambie el comportamiento! Para eso están tus exámenes, para decirte cuando rompes algo :)

Métodos públicos en la misma clase.

Eso no sucede muy a menudo, ¿verdad? Y si le gusta en el siguiente ejemplo, hay algunas maneras de manejar esto:

$stuff = new Stuff();
$stuff->setBla(12);
$stuff->setFoo(14);
$stuff->execute(); 

Que los establecedores existan y no formen parte de la firma del método de ejecución es otro tema;)

Lo que podemos probar aquí es si las ejecuciones explotan cuando establecemos los valores incorrectos. Eso setBlaarroja una excepción cuando pasa una cadena se puede probar por separado, pero si queremos probar que esos dos valores permitidos (12 y 14) no funcionan JUNTOS (por cualquier razón) que ese es un caso de prueba.

Si desea un conjunto de pruebas "bueno" puede, en php, quizás (!) Agregar una @covers Stuff::executeanotación para asegurarse de que solo genera cobertura de código para este método y las otras cosas que solo se configuran deben probarse por separado (nuevamente, si quieres eso).

Entonces, el punto es: tal vez primero necesite crear algo del mundo circundante, pero debería poder escribir casos de prueba significativos que generalmente solo abarcan una o tal vez dos funciones reales (los setters no cuentan aquí). El resto se puede burlar del éter o probar primero y luego confiar en él (ver @depends)


* Nota: La pregunta se migró de SO e inicialmente era sobre PHP / PHPUnit, por eso el código de muestra y las referencias son del mundo php, creo que esto también es aplicable a otros idiomas, ya que phpunit no difiere mucho de otros xUnit marcos de prueba.

edoriano
fuente
Muy detallado e informativo ... Usted dijo "Desea probar comportamientos, no solo métodos únicos sin contexto", seguramente no se puede probar una cantidad decente de métodos dentro de un contexto particular porque dependen o dependen de otros métodos , por lo tanto, una condición de prueba útil solo sería contextual si los dependientes también se estuvieran probando? ¿O estoy malinterpretando lo que quieres decir>
zcourts
@ robinsonc494 Lo editaré en un ejemplo que tal vez explica un poco mejor a dónde voy con esto
edorian
gracias por las ediciones y el ejemplo, ciertamente ayuda. Creo que mi confusión (si se puede llamar así) fue que, aunque leí acerca de las pruebas de "comportamiento", de alguna manera pensé inherentemente (¿tal vez?) En casos de prueba centrados en la implementación.
zcourts
@ robinsonc494 Tal vez piense de esta manera: si golpea a alguien que él responde, llame a la policía o huya. Ese comportamiento Es lo que hace la persona. El hecho de que use sus mejillones provocados por pequeñas cargas eléctricas de su cerebro es la implementación. Si quieres probar la reacción de alguien, dale un puñetazo y mira si actúa como lo esperas. No lo pones en un escáner cerebral y ves si los impulsos se envían a los mejillones. Algunos van a clases más o menos;)
edorian
3
Creo que el mejor ejemplo que vi de TDD que realmente lo ayudó a hacer clic en cómo escribir los casos de prueba fue el Bowling Game Kata del tío Bob Martin. slideshare.net/lalitkale/bowling-game-kata-by-robert-c-martin
Amy Anuszewski
2

Las pruebas y las pruebas unitarias no son lo mismo. Unit Testing es un subconjunto muy importante e interesante de Testing en general. Yo diría que el enfoque de las Pruebas unitarias nos hace pensar en este tipo de pruebas de una manera que contradice las citas anteriores.

Primero, si seguimos TDD o incluso DTH (es decir, desarrollamos y probamos en estrecha armonía) estamos usando las pruebas que escribimos como un enfoque para hacer que nuestro diseño sea correcto. Al pensar en casos de esquina y escribir pruebas en consecuencia, evitamos que los errores entren en primer lugar, por lo que, de hecho, escribimos una prueba que esperamos pasar (OK al comienzo de TDD fallan, pero eso es solo un artefacto de pedido, cuando el código está hecho, esperamos que lo aprueben, y la mayoría lo hace, porque usted ha pensado en el código.

En segundo lugar, las pruebas unitarias realmente se hacen realidad cuando refactorizamos. Cambiamos nuestra implementación, pero esperamos que las respuestas sigan siendo las mismas: la Prueba de Unidad es nuestra protección contra la ruptura de nuestro contrato de interfaz. Así que una vez más esperamos que pasen las pruebas.

Esto implica que para nuestra interfaz pública, que presumiblemente es estable, necesitamos una trazabilidad clara para que podamos ver que todos los métodos públicos se prueban.

Para responder a su pregunta explícita: Prueba de unidad para interfaz pública tiene valor.

editado en respuesta comentario:

Prueba de métodos privados? Sí, deberíamos, pero si no tenemos que probar algo, ahí es donde me comprometería. Después de todo, si los métodos públicos están funcionando, ¿pueden ser tan importantes esos errores en lo privado ? Pragmáticamente, la rotación tiende a suceder en las cosas privadas, trabajas duro para mantener tu interfaz pública, pero si las cosas de las que dependes cambian, las cosas privadas pueden cambiar. En algún momento podemos encontrar que mantener las pruebas internas es un gran esfuerzo. ¿Es ese esfuerzo bien empleado?

djna
fuente
Entonces, solo para asegurarme de que entiendo lo que estás diciendo: cuando pruebes, concéntrate en probar la interfaz pública, ¿verdad? Asumiendo que eso es correcto ... ¿no hay una mayor posibilidad de dejar errores en métodos / interfaces privados para los que no realizó pruebas unitarias, algunos errores difíciles en la interfaz "privada" no probada podrían conducir a una prueba aprobada cuando Debería haber fallado realmente. ¿Me equivoco al pensar eso?
zcourts
Al usar la cobertura de código, puede saber cuándo no se ejecuta el código en sus métodos privados al probar sus métodos públicos. Si sus métodos públicos están completamente cubiertos, los métodos privados no cubiertos obviamente no se utilizan y pueden eliminarse. Si no, necesita más pruebas para sus métodos públicos.
David Harkness
2

Las pruebas unitarias deben ser parte de una estrategia de prueba más amplia. Sigo estos principios al elegir qué tipos de pruebas escribir y cuándo:

  • Concéntrese en escribir exámenes de punta a punta. Cubres más código por prueba que con las pruebas unitarias y, por lo tanto, obtienes más beneficios por el dinero. Haga de estos la validación automatizada de su sistema en su conjunto.

  • Vaya a escribir pruebas unitarias en torno a pepitas de lógica complicada. Las pruebas unitarias valen su peso en situaciones donde las pruebas de extremo a extremo serían difíciles de depurar o difíciles de escribir para una cobertura de código adecuada.

  • Espere hasta que la API con la que está probando sea estable para escribir cualquier tipo de prueba. Desea evitar tener que refactorizar tanto su implementación como sus pruebas.

Rob Ashton tiene un excelente artículo sobre este tema, del cual extraje mucho para articular los principios anteriores.

Edward Brey
fuente
+1: no estoy necesariamente de acuerdo con todos sus puntos (o los puntos del artículo), pero sí estoy de acuerdo con que la mayoría de las pruebas unitarias son inútiles si se realizan a ciegas (enfoque TDD). Sin embargo, son extremadamente valiosos si eres inteligente al decidir qué es lo que vale la pena pasar tiempo haciendo pruebas unitarias. Estoy totalmente de acuerdo en que obtiene mucho, mucho más por el dinero al escribir pruebas de nivel superior, en particular pruebas automatizadas de nivel de subsistema. El problema con las pruebas automatizadas de extremo a extremo es que serían difíciles, si no totalmente poco prácticas, para un sistema si tiene algún tamaño / complejidad.
Dunk
0

Tiendo a seguir un enfoque diferente para las pruebas unitarias que parece funcionar bien. En lugar de pensar en una prueba unitaria como "Prueba de algún comportamiento", lo considero más como "una especificación que mi código debe seguir". De esta manera, básicamente puede declarar que un objeto debe comportarse de una manera determinada, y dado que asume que en otra parte de su programa, puede estar bastante seguro de que está relativamente libre de errores.

Si está escribiendo una API pública, esto es extremadamente valioso. Sin embargo, siempre necesitará una buena dosis de pruebas de integración de extremo a extremo, porque acercarse al 100% de cobertura de prueba unitaria generalmente no vale la pena y perderá cosas que la mayoría consideraría "no comprobables" por los métodos de prueba unitaria (burlas, etc)

Earlz
fuente