Hemos escrito cerca de 3.000 pruebas: los datos han sido codificados, muy poca reutilización del código. Esta metodología ha comenzado a mordernos en el culo. A medida que cambia el sistema, nos encontramos pasando más tiempo arreglando pruebas rotas. Contamos con pruebas unitarias, integrales y funcionales.
Lo que estoy buscando es una forma definitiva de escribir pruebas manejables y mantenibles.
Marcos
.net
unit-testing
Chuck Conway
fuente
fuente
Respuestas:
No pienses en ellos como "pruebas de unidades rotas", porque no lo son.
Son especificaciones que su programa ya no admite.
No piense en ello como "arreglar las pruebas", sino como "definir nuevos requisitos".
Las pruebas deben especificar su aplicación primero, no al revés.
No puede decir que tiene una implementación funcional hasta que sepa que funciona. No puedes decir que funciona hasta que lo pruebes.
Algunas otras notas que pueden guiarte:
fuente
Don't think of it as "fixing the tests", but as "defining new requirements".
Lo que usted describe en realidad puede no ser tan malo, sino un indicador de problemas más profundos que descubren sus pruebas
Si pudiera cambiar su código, y sus pruebas no se romperían, eso sería sospechoso para mí. La diferencia entre un cambio legítimo y un error es solo el hecho de que se solicita, y lo que se solicita es (se supone TDD) definido por sus pruebas.
Los datos codificados en las pruebas son, en mi opinión, algo bueno. Las pruebas funcionan como falsificaciones, no como pruebas. Si hay demasiado cálculo, sus pruebas pueden ser tautologías. Por ejemplo:
Cuanto mayor sea la abstracción, más se acercará al algoritmo y, por eso, más cerca de comparar la implementación aguda con sí mismo.
La mejor reutilización de código en las pruebas es en mi opinión 'Verificaciones', como en jUnits
assertThat
, porque mantienen las pruebas simples. Además de eso, si las pruebas pueden ser refactorizadas para compartir código, el código real probado probablemente también lo sea , reduciendo así las pruebas a las que prueban la base refactorizada.fuente
También he tenido este problema. Mi enfoque mejorado ha sido el siguiente:
No escriba pruebas unitarias a menos que sean la única buena manera de probar algo.
Estoy completamente preparado para admitir que las pruebas unitarias tienen el menor costo de diagnóstico y tiempo de reparación. Esto los convierte en una herramienta valiosa. El problema es, con el obvio su-millaje-puede-variar, que las pruebas unitarias a menudo son demasiado pequeñas para merecer el costo de mantener la masa del código. Escribí un ejemplo en la parte inferior, eche un vistazo.
Utilice aserciones siempre que sean equivalentes a la prueba unitaria para ese componente. Las aserciones tienen la buena propiedad de que siempre se verifican en cualquier compilación de depuración. Entonces, en lugar de probar las restricciones de clase "Empleado" en una unidad de pruebas separada, está probando efectivamente la clase Empleado a través de cada caso de prueba en el sistema. Las afirmaciones también tienen la buena propiedad de que no aumentan la masa del código tanto como las pruebas unitarias (que eventualmente requieren andamios / burlas / lo que sea).
Antes de que alguien me mate: las compilaciones de producción no deberían fallar en las afirmaciones. En su lugar, deberían iniciar sesión en el nivel "Error".
Como advertencia a alguien que aún no lo ha pensado, no afirme nada sobre la entrada del usuario o la red. Es un gran error ™.
En mis últimas bases de código, he estado eliminando juiciosamente las pruebas unitarias siempre que veo una oportunidad obvia de afirmaciones. Esto ha reducido significativamente el costo de mantenimiento en general y me ha hecho una persona mucho más feliz.
Prefiera las pruebas de sistema / integración, implementándolas para todos sus flujos primarios y experiencias de usuario. Los casos de esquina probablemente no necesiten estar aquí. Una prueba del sistema verifica el comportamiento al final del usuario ejecutando todos los componentes. Debido a eso, una prueba del sistema es necesariamente más lenta, así que escriba las que importen (ni más ni menos) y detectará los problemas más importantes. Las pruebas del sistema tienen gastos generales de mantenimiento muy bajos.
Es clave recordar que, dado que está utilizando afirmaciones, cada prueba del sistema ejecutará un par de cientos de "pruebas unitarias" al mismo tiempo. También está bastante seguro de que los más importantes se ejecutan varias veces.
Escriba API fuertes que puedan probarse funcionalmente. Las pruebas funcionales son incómodas y (seamos sinceros) algo sin sentido si su API hace que sea demasiado difícil verificar los componentes que funcionan por sí mismos. Un buen diseño de API a) simplifica los pasos de prueba yb) genera afirmaciones claras y valiosas.
La prueba funcional es lo más difícil de hacer, especialmente cuando tienes componentes que se comunican de uno a muchos o (aún peor, oh dios) de muchos a muchos a través de las barreras del proceso. Cuantas más entradas y salidas se unan a un solo componente, más difícil será la prueba funcional, ya que debe aislar una de ellas para probar realmente su funcionalidad.
Sobre el tema de "no escribir pruebas unitarias", presentaré un ejemplo:
El autor de esta prueba ha agregado siete líneas que no contribuyen en absoluto a la verificación del producto final. El usuario nunca debería ver que esto suceda, ya sea porque a) nadie debería pasar NULL allí (entonces escriba una afirmación, entonces) ob) el caso NULL debería causar un comportamiento diferente. Si el caso es (b), escriba una prueba que realmente verifique ese comportamiento.
Mi filosofía se ha convertido en que no debemos probar los artefactos de implementación. Solo debemos probar cualquier cosa que pueda considerarse una salida real. De lo contrario, no hay forma de evitar escribir el doble de la masa básica de código entre las pruebas unitarias (que fuerzan una implementación particular) y la implementación misma.
Es importante tener en cuenta, aquí, que hay buenos candidatos para las pruebas unitarias. De hecho, incluso hay varias situaciones en las que una prueba unitaria es el único medio adecuado para verificar algo y en el que es de gran valor escribir y mantener esas pruebas. De la parte superior de mi cabeza, esta lista incluye algoritmos no triviales, contenedores de datos expuestos en una API y código altamente optimizado que parece "complicado" (también conocido como "el próximo tipo probablemente lo arruinará").
Mi consejo específico para usted, entonces: Comience a eliminar juiciosamente las pruebas unitarias a medida que se rompen, preguntándose: "¿Es esto una salida o estoy desperdiciando código?" Probablemente logre reducir la cantidad de cosas que le hacen perder el tiempo.
fuente
Me parece que la prueba de tu unidad funciona a las mil maravillas. Es bueno que sea tan frágil a los cambios, ya que ese es el punto principal. Pequeños cambios en las pruebas de ruptura de código para que pueda eliminar la posibilidad de error en todo su programa.
Sin embargo, tenga en cuenta que realmente solo necesita probar las condiciones que harían que su método fallara o que diera resultados inesperados. Esto haría que su unidad de prueba sea más propensa a "romperse" si hay un problema genuino en lugar de cosas triviales.
Aunque me parece que estás rediseñando mucho el programa. En tales casos, haga lo que sea necesario y elimine las pruebas anteriores y reemplácelas por otras nuevas después. Reparar pruebas unitarias solo vale la pena si no está reparando debido a cambios radicales en su programa. De lo contrario, es posible que dedique demasiado tiempo a la reescritura de pruebas para que sea aplicable en su nueva sección del código del programa.
fuente
Estoy seguro de que otros tendrán mucho más aporte, pero en mi experiencia, estas son algunas cosas importantes que lo ayudarán:
fuente
Maneje las pruebas como lo hace con el código fuente.
Control de versiones, lanzamiento de puntos de control, seguimiento de problemas, "propiedad de características", planificación y estimación de esfuerzos, etc.
fuente
Definitivamente deberías echar un vistazo a los patrones de prueba XUnit de Gerard Meszaros . Tiene una gran sección con muchas recetas para reutilizar su código de prueba y evitar duplicaciones.
Si sus pruebas son frágiles, también podría ser que no recurra lo suficiente como para probar dobles. Especialmente, si recrea gráficos enteros de objetos al comienzo de cada prueba unitaria, las secciones de Arreglo en sus pruebas pueden sobredimensionarse y a menudo puede encontrarse en situaciones en las que tiene que reescribir las secciones de Arreglo en un número considerable de pruebas solo porque una de sus clases más utilizadas ha cambiado. Los simulacros y trozos pueden ayudarlo aquí al reducir la cantidad de objetos que tiene que rehidratar para tener un contexto de prueba relevante.
Eliminar los detalles sin importancia de sus configuraciones de prueba a través de simulacros y apéndices y aplicar patrones de prueba para reutilizar el código debería reducir su fragilidad significativamente.
fuente