2010-02-11 14 views
25

Recientemente terminé un proyecto usando TDD y encontré que el proceso era un poco una pesadilla. Disfruté primero escribiendo pruebas y viendo crecer mi código, pero tan pronto como los requisitos comenzaron a cambiar y empecé a hacer refactorizaciones, descubrí que dedicaba más tiempo a reescribir/corregir pruebas de unidades que escribir código, mucho más tiempo, de hecho.Pruebas de Unidad Maintainable

Sentí que mientras estaba pasando por este proceso sería mucho más fácil hacer las pruebas después de que la aplicación estuviera terminada, pero si lo hiciera, habría perdido todos los beneficios de TDD.

Entonces, ¿hay algún acierto/sugerencia para escribir un código TDD que se pueda mantener? Actualmente estoy leyendo The Art Of Unit Testing de Roy Osherove, ¿hay algún otro recurso que pueda ayudarme?

Gracias

+0

Para enmendar un ejemplo, un día decidí que el nombre de una propiedad en mi código era incorrecto, así que lo cambié a uno mejor. Esta propiedad se usó en aproximadamente 30 pruebas diferentes, así que tuve que pasar por cada prueba y cambiarla manualmente, eso lleva mucho tiempo. Estoy buscando algunos hits/tips prácticos, como resharper, usando la palabra clave var, etc. –

+2

@Lee Tevail: ¿estás diciendo que (a) tu IDE no tiene búsqueda y reemplazo globales y (b) TDD es la causa raíz de tu IDE? ¿tan pobre? –

+0

Tampoco, no odio TDD. Estoy buscando formas de hacer las cosas de manera más eficiente. –

Respuesta

16

Práctica

Se toma un tiempo para aprender cómo escribir pruebas unitarias decente. Un proyecto difícil (más como proyectos) no es nada extraño.

El libro xUnit Test Patterns recomendado ya es bueno, y he oído cosas buenas sobre el libro que está leyendo actualmente.

En cuanto a los consejos generales, depende de lo que fue duro con sus pruebas. Si se rompieron a menudo, pueden no ser pruebas unitarias, y más aún pruebas de integración. Si fueran difíciles de configurar, el SUT (Sistema Bajo Prueba) podría mostrar signos de ser demasiado complejo y necesitaría una mayor modularización. La lista continua.

Algunos consejos que defiendo están siguiendo la regla AAA.

Organizar, actuar y afirmar. Cada prueba debe seguir esta fórmula. Esto hace que la prueba sea legible y fácil de mantener siempre y cuando se rompa.

Diseño sigue siendo importante

practico TDD, pero antes de que se escribió ningún código agarro una pizarra y garabatos de distancia. Mientras TDD permite que su código evolucione, algunos diseños iniciales son siempre un beneficio. Entonces al menos tienes un punto de partida, desde aquí tu código puede ser impulsado por las pruebas que escribas.

Si estoy llevando a cabo una tarea particularmente difícil, hago un prototipo. Olvídese de TDD, olvídese de las mejores prácticas, simplemente descifre algún código. Obviamente, esto no es código de producción, pero proporciona un punto de partida. A partir de este prototipo, pienso en el sistema real y en las pruebas que necesito.

Echa un vistazo a Google Testing Blog - este fue el punto de inflexión para mí mismo al iniciar TDD.Los artículos de Misko (y el sitio - el código Guide to Testable especialmente) son excelentes, y deben apuntar en la dirección correcta.

+0

+1. Me encanta la referencia a los artículos de Misko. Considero que también son un punto de inflexión para mí. –

2

Sí, hay un libro entero llamado xUnit Test Patterns que se ocupan de este problema.

Es un libro de autor de Martin Fowler, por lo que tiene todas las características de un libro de patrones clásicos. Si te gusta o no es una cuestión de gusto personal, pero, por mi parte, me pareció inmensamente invaluable.

De todos modos, la esencia del asunto es que debe tratar su código de prueba como si fuera su código de producción. En primer lugar, debe cumplir con el principio DRY, porque eso facilita la refactorización de su API.

2

¿Está haciendo un uso liberal de interfaces, Dependance Injection y Mocking?

Me parece que diseñar interfaces e inyectar implementaciones de esas interfaces usando un marco DI como ninject hace que sea mucho más fácil simular partes de la aplicación para que se prueben correctamente los componentes de forma aislada.

Esto hace que sea más fácil hacer cambios en un área sin que afecte demasiado a otros, o si los cambios necesitan propagarse, puede simplemente actualizar las interfaces y trabajar a través de cada área distinta a la vez.

7

"tan pronto como los requisitos empezaron a cambiar y me empezaron a hacer refactorizaciones he encontrado que pasé más tiempo volver a escribir/fijación pruebas unitarias"

Así? ¿Cómo es esto un problema?

Sus necesidades han cambiado. Eso significa que tu diseño tuvo que cambiar. Eso significa que tus pruebas tuvieron que cambiar.

"Pasé más tiempo reescribiendo/fijando pruebas de unidades que escribiendo el código, mucho más tiempo de hecho".

Eso significa lo estás haciendo bien. Los requisitos, el diseño y el impacto de la prueba formaron parte de las pruebas y su aplicación no requirió muchos cambios.

Así se supone que debe funcionar.

Vaya a casa feliz. Has hecho el trabajo correctamente.

+0

Pensar así es exactamente la razón por la que no adopto el TDD.El trabajo de un programador no es escribir pruebas, sino producir código bueno y funcional. Si lleva más tiempo realizar la verificación de que el código es bueno que crear el código real, creo que algo tiene serias fallas en el proceso. – erikkallen

+7

@erikkallen: El trabajo de un programador es "producir código de trabajo, bueno" * en el que tenga plena confianza *. No puedo enfatizar la * completa confianza * suficiente. Las pruebas son una buena forma de aumentar la confianza en el código. Debería * tomar * un tiempo para verificar que el código es bueno. TDD no cambia eso. Nada cambia eso. La confianza requiere mucho cuidado, ya sea una gran cantidad de pruebas o una prueba cuidadosamente construida. O ambos. –

3

Parece que las pruebas de su unidad son frágiles y superpuestas. Un cambio de código único, idealmente, debería afectar solo una prueba de la unidad: con una coincidencia uno a uno de las pruebas con las características, otras pruebas no dependen de una función determinada. Eso puede ser un poco demasiado idealista; en la práctica, muchas de nuestras pruebas vuelven a ejercitar el mismo código, pero es algo a tener en cuenta. Cuando un cambio de código afecta muchas pruebas, es un olor. Además, con respecto a su ejemplo específico de cambio de nombre: encuentre una herramienta que automatice estas refactorizaciones por usted. Creo que Resharper y CodeRush son compatibles con estas refactorizaciones automatizadas; es una forma mucho más rápida, fácil y confiable de refactorizar que el enfoque manual.

Para conocer mejor su IDE, nada mejor que emparejarse con otra persona. Ambos aprenderán; ambos desarrollarán nuevas habilidades, y no llevará mucho tiempo. Unas pocas horas aumentarán drásticamente su comodidad con la herramienta.

+0

+1 para obtener recomendaciones sobre el emparejamiento para aprender el IDE. Me gusta tomar medio día cada dos semanas más o menos, y utilizar el tiempo únicamente para aprender algo nuevo sobre mi IDE o alguna otra herramienta. Paga grandes dividendos en el largo plazo. –

2

Creo que es posible que desee lograr un equilibrio aceptable entre las pruebas y la codificación.

Cuando comienzo un proyecto, ya que los requisitos y objetivos cambian constantemente, casi no escribo ninguna prueba, porque, como observó, tomaría mucho tiempo arreglar constantemente las pruebas. En algún momento escribí un comentario "esto debería probarse" para no olvidar probarlo.

En algún momento siente que su proyecto se está poniendo en forma. Ese es el momento en que las pruebas de unidades pesadas son útiles. Escribo tanto como sea posible.

Cuando empiezo a hacer mucha refacturación, no me preocupo demasiado por las pruebas hasta que el proyecto se haya estabilizado nuevamente. También puse algunos comentarios de "prueba esto".Cuando termine la refactorización, es hora de volver a escribir todas las pruebas que fallan (y quizás deshacerse de algunas, y ciertamente escribir algunas nuevas).

Escribir pruebas de esta manera es realmente un placer, porque se da cuenta de que sus proyectos han alcanzado un hito.

5

Soy un gran admirador de las pruebas unitarias, pero he tenido problemas con TDD (o pruebas de unidad básica para el caso) en mi proyecto más reciente. Después de realizar una revisión posterior a la implementación, descubrí que nosotros (yo y el resto del equipo) enfrentamos dos problemas principales con nuestra implementación/comprensión de TDD y pruebas unitarias.

El primer problema que enfrentamos fue que no siempre tratamos nuestras pruebas como ciudadanos de primera clase. Sé que esto parece ir en contra de la filosofía de TDD, pero nuestros problemas surgieron después de que habíamos hecho la mayor parte del diseño inicial y nos apresuramos a hacer cambios sobre la marcha. Lamentablemente, debido a limitaciones de tiempo, la última parte del proyecto se apresuró y caímos en la trampa de escribir nuestras pruebas después de que se había escrito el código. Como el código de trabajo montado a presión se controló en el control de fuente sin verificar si las pruebas de la unidad aún pasaron. Es cierto que este problema no tiene nada que ver con el TDD o las pruebas unitarias, sino que fue el resultado de plazos ajustados, comunicación promedio del equipo y liderazgo deficiente (me voy a culpar a mí mismo aquí).

Cuando miramos un poco más profundo en las pruebas de la unidad que falla, descubrimos que estábamos probando demasiado, especialmente teniendo en cuenta nuestras limitaciones de tiempo. En lugar de usar TDD y enfocar nuestras pruebas en el código con un alto rendimiento, utilizamos TDD y pruebas de escritura para todo el código base. Esto hizo que nuestra proporción de pruebas unitarias codificara mucho más de lo que podríamos mantener. Nosotros (eventualmente) decidimos usar solo TDD y escribir pruebas para la funcionalidad comercial que probablemente cambiaría. Esto redujo nuestra necesidad de mantener una gran cantidad de pruebas que en su mayoría muy raramente (o nunca) cambiaron. En cambio, nuestros esfuerzos estuvieron mejor enfocados e hicieron un conjunto más completo de pruebas sobre las partes de la aplicación que realmente nos importaban.

Afortunadamente, puedo aprender de mis experiencias y continuar desarrollando TDD o, al menos, desarrollar pruebas unitarias para su código. Personalmente, encontré los siguientes enlaces extremadamente útiles para ayudarme a comprender conceptos tales como las pruebas unitarias selectivas.

1

En primer lugar, la refactorización no rompe las pruebas unitarias. ¿Aplicaste la refactorización por the book? Puede ser que sus pruebas estén probando la implementación y no el comportamiento, lo que puede explicar por qué se rompen.

Las pruebas unitarias deben ser prueba de caja negra, pruebe lo que hace la unidad, no cómo lo hace.

0

¿Está utilizando un buen IDE? Me estaba haciendo la misma pregunta que hace unos años cuando abracé por primera vez las pruebas unitarias. En aquel entonces, estaba usando una combinación de Emacs, find y grep para hacer refactorizaciones. Fue doloroso.

Afortunadamente, un colega me dio un golpe en la cabeza y me convenció de que intentara usar "herramientas modernas", que en su lengua vernácula significaba Intellij IDEA. IDEA es mi preferencia personal, pero Netbeans o Eclipse también manejarán lo básico. Es difícil exagerar la ganancia de productividad que esto me proporcionó; fácilmente una ganancia de orden de magnitud, especialmente para proyectos grandes con muchas pruebas.

Una vez que tenga un IDE cuadrado de distancia, si usted es todavía se produzcan problemas es el momento de considerar la DRY principle, que busca asegurar que la información se mantiene en un solo lugar (archivo constante, propiedad, etc.) por lo que si necesita cambiarlo más tarde, los efectos de onda se minimizan.

0

Si sus pruebas son difíciles de mantener, es una señal de que su código es frágil. Significa que la definición de la clase está cambiando con frecuencia.

Considere definir las responsabilidades de una clase como las llamadas que realiza a una interfaz inyectada. Piénselo más en términos de pasar datos o enviar un mensaje en lugar de manipular el estado.

1

Encontré una herramienta de prueba de desarrollador semiautomatizada para Java llamada SpryTest. Proporciona una interfaz de usuario simple pero potente para crear datos aleatorios. También admite llamadas simuladas usando powermock y easymock. Puede generar pruebas estándar de JUnit que están más cerca de las pruebas escritas a mano. Tiene la característica de sincronización de código de prueba-> fuente.

Lo probé y funcionó bastante bien para mí. Consulte la herramienta en http://www.sprystone.com

Cuestiones relacionadas