Lo que se entiende bajo "unidad" en pruebas unitarias

9

Como entiendo en teoría bajo "unidad", la gente quiere decir método (en OOP). Pero en la práctica, las pruebas que verifican algún método de forma aislada son pruebas de comportamiento muy frágiles (no se verifica el resultado sino el hecho de que se llamó a algún método de dependencia). Así que veo mucha gente que, por unidad, comprende un pequeño conjunto de clases estrechamente relacionadas. En este caso, solo las dependencias externas se burlan / se tropezan y para las dependencias que están dentro de la unidad se usan implementaciones reales. En este caso hay más pruebas de estado, significativas (según la especificación) y no tan frágiles. Entonces, la pregunta es ¿cómo se siente acerca de estos enfoques? ¿Es válido llamar a la segunda prueba de unidad de enfoque o puede ser algún tipo de prueba de integración de bajo nivel?

Si ve algunas consideraciones específicas sobre la aplicación de TDD por una de estas formas de prueba, le agradecería su opinión.

SiberianGuy
fuente

Respuestas:

11

Originalmente, TDD provenía del movimiento ágil, donde las pruebas se escribieron por adelantado como una forma de garantizar que lo que codificó se mantuvo correcto dada la especificación que ahora estaba bien definida en el código de prueba. También apareció como un aspecto muy importante de la refactorización, ya que cuando modificaba su código, podía confiar en las pruebas para demostrar que no había cambiado el comportamiento del código.

Luego, las personas llegaron y pensaron que sabían información sobre su código y luego pudieron generar comprobantes de prueba para ayudarlo a escribir sus pruebas unitarias, y creo que aquí es donde todo salió mal.

Los resguardos de prueba son generados por una computadora que no tiene idea de lo que está haciendo, simplemente produce un resguardo para cada método porque eso es lo que se le dice que haga. Esto significa que tiene un caso de prueba para cada método, independientemente de la complejidad de ese método o si es adecuado para realizar pruebas de forma aislada.

Esto viene en las pruebas desde el lado equivocado de la metodología TDD. En TDD, se supone que debe averiguar qué debe hacer el código y luego generar un código que lo logre. Esto es autocumplido, ya que terminas escribiendo pruebas que prueban que el código hace lo que hace, no lo que se supone que debe hacer. En combinación con la generación automática de comprobantes de prueba basados ​​en métodos, prácticamente pierdes tu tiempo probando cada pequeño aspecto de tu código que fácilmente puede resultar incorrecto cuando se juntan todas las piezas pequeñas.

Cuando Fowler describió las pruebas en su libro, se refirió a probar cada clase con su propio método principal. Él mejoró esto, pero el concepto sigue siendo el mismo: se prueba toda la clase para que funcione como un todo, todas sus pruebas se agrupan para probar la interacción de todos esos métodos para que la clase se pueda reutilizar con expectativas definidas.

Creo que los kits de herramientas de prueba nos han perjudicado, nos llevaron a pensar que el kit de herramientas es la única forma de hacer las cosas cuando realmente necesita pensar más para obtener el mejor resultado de su código. Poner a ciegas el código de prueba en trozos de prueba para piezas pequeñas solo significa que tienes que repetir tu trabajo en una prueba de integración de todos modos (y si vas a hacer eso, ¿por qué no omitir la etapa de prueba de unidad ahora redundante por completo). También significa que las personas pierden mucho tiempo tratando de obtener una cobertura de prueba del 100%, y mucho tiempo creando grandes cantidades de código de burla y datos que se habrían gastado mejor haciendo que el código sea más fácil para la prueba de integración (es decir, si tiene tanto dependencias de datos, la prueba unitaria podría no ser la mejor opción)

Por último, la fragilidad de las pruebas unitarias basadas en métodos simplemente muestra el problema. La refactorización está diseñada para usarse con pruebas unitarias, si sus pruebas se rompen todo el tiempo porque está refactorizando, entonces algo ha salido muy mal con todo el enfoque. Refactorizar le gusta crear y eliminar métodos, por lo que, obviamente, el enfoque de prueba ciego por método no es lo que se pretendía originalmente.

No tengo dudas de que muchos métodos obtendrán pruebas escritas para ellos, todos los métodos públicos de una clase deben ser probados, pero no puede alejarse del concepto de probarlos juntos como parte de un solo caso de prueba. Por ejemplo, si tengo un conjunto y un método get, puedo escribir pruebas que coloquen los datos y verificar que los miembros internos estén configurados correctamente, o puedo usar cada uno para poner algunos datos y luego sacarlos nuevamente para ver si son sigue siendo el mismo y no está distorsionado. Esto prueba la clase, no cada método de forma aislada. Si el setter se basa en un método privado auxiliar, entonces está bien: no necesita burlarse del método privado para asegurarse de que el setter esté funcionando, no si prueba toda la clase.

Creo que la religión se está metiendo en este tema, por lo tanto, se ve el cisma en lo que ahora se conoce como desarrollo 'impulsado por el comportamiento' y 'impulsado por la prueba': el concepto original de pruebas unitarias era para el desarrollo impulsado por el comportamiento.

gbjbaanb
fuente
10

Una unidad se define más comúnmente como " la parte comprobable más pequeña de una aplicación ". La mayoría de las veces, sí, esto significa un método. Y sí, esto significa que no debe probar el resultado de un método dependiente, sino solo que se llama al método (y luego solo una vez, si es posible, no en todas las pruebas para ese método).

A esto lo llamas frágil. Creo que eso es incorrecto. Las pruebas frágiles son aquellas que se rompen bajo el más mínimo cambio a código no relacionado. Es decir, aquellos que dependen del código que es irrelevante para la funcionalidad que se está probando.

Sin embargo, lo que creo que realmente quiere decir es que probar un método sin ninguna de sus dependencias no es exhaustivo. En ese punto estaría de acuerdo. También necesita pruebas de integración para asegurarse de que las unidades de código estén correctamente conectadas para crear una aplicación.

Este es exactamente el problema que el desarrollo impulsado por el comportamiento , y específicamente el desarrollo impulsado por la prueba de aceptación , se propone resolver. Pero eso no elimina la necesidad de pruebas unitarias / desarrollo basado en pruebas; simplemente lo complementa.

pdr
fuente
Realmente quería decir que las pruebas de comportamiento son frágiles. A menudo se vuelven falsos negativos durante el cambio de la base de código. Sucede menos con las pruebas estatales (pero las pruebas estatales rara vez son para pruebas unitarias)
SiberianGuy
@Idsa: Estoy un poco perdido por tus definiciones. Las pruebas de comportamiento son pruebas de integración, que prueban un comportamiento como se especifica. Al leer su pregunta original, parece que cuando dice pruebas estatales, quiere decir lo mismo.
pdr
por estado quiero decir prueba que verifica el estado, resultado de alguna función; por medio del análisis behavour I que no verifica el resultado, pero el hecho de que alguna función se llama
SiberianGuy
@Idsa: En ese caso, estoy completamente en desacuerdo. Lo que llamas prueba de estado, lo llamo integración. Lo que llamas comportamiento, lo llamo unidad. Las pruebas de integración por su propia definición son más frágiles. Google "unidad de prueba de integración frágil" y verá que no estoy solo.
pdr
Hay un registro de artículos sobre pruebas, pero ¿cuál de ellos comparte su opinión?
SiberianGuy
2

Como su nombre indica, está probando un sujeto atómico en cada prueba. Tal tema suele ser un método único. Múltiples pruebas pueden probar el mismo método, para cubrir el camino feliz, posibles errores, etc. Está probando el comportamiento, no la mecánica interna. Por lo tanto, las pruebas unitarias se tratan realmente de probar la interfaz pública de una clase, es decir, un método específico.

En las pruebas unitarias, un método debe probarse de forma aislada, es decir, tropezando / burlándose / falsificando cualquier dependencia. De lo contrario, probar una unidad con dependencias 'reales' la convierte en una prueba de integración. Hay un momento y lugar para ambos tipos de pruebas. Las pruebas unitarias aseguran que un solo sujeto funcione como se esperaba, independiente. Las pruebas de integración aseguran que los sujetos 'reales' trabajen juntos correctamente.

Grant Palin
fuente
1
no del todo, una unidad es un tema aislado, solo porque las herramientas automatizadas prefieren tratar un método ya que esto no lo hace así, ni lo hace el mejor. 'Aislado' es la clave aquí. Incluso si prueba métodos, también debe probar los privados.
gbjbaanb
1

Mi regla de oro: la unidad de código más pequeña que sigue siendo lo suficientemente compleja como para contener errores.

Si este es un método o clase o subsistema depende del código particular, no se puede dar una regla general.

Por ejemplo, no proporciona ningún valor para probar métodos getter / setter simples de forma aislada, o métodos wrapper que solo llaman a otro método. Incluso una clase completa puede ser demasiado simple para probar, si la clase es solo un envoltorio o adaptador delgado. Si lo único que se debe probar es si se llama a un método simulado, entonces el código que se está probando es delgado.

En otros casos, un solo método puede realizar algunos cálculos complejos que son valiosos para probar de forma aislada.

En muchos casos, las partes complejas no son clases individuales sino más bien la integración entre clases. Entonces prueba dos o más clases a la vez. Algunos dirán que esto no son pruebas unitarias sino pruebas de integración, pero no importa la terminología: debe probar dónde está la complejidad, y estas pruebas deberían ser parte del conjunto de pruebas.

JacquesB
fuente