Soy bastante nuevo en TDD y tengo problemas al crear mi primera prueba cuando se trata de cualquier código de implementación. Sin ningún marco para el código de implementación, soy libre de escribir mi primera prueba como quiera, pero siempre parece estar contaminada por mi forma de pensar Java / OO sobre el problema.
Por ejemplo, en mi Github ConwaysGameOfLifeExample, la primera prueba que escribí (rule1_zeroNeighbours) comencé creando un objeto GameOfLife que aún no se había implementado; llamó un método de conjunto que no existía, un método de paso que no existía, un método de obtención que no existía, y luego usó una afirmación.
Las pruebas evolucionaron a medida que escribía más pruebas y las refactorizaba, pero originalmente se parecía a esto:
@Test
public void rule1_zeroNeighbours()
{
GameOfLife gameOfLife = new GameOfLife();
gameOfLife.set(1, 1, true);
gameOfLife.step();
assertEquals(false, gameOfLife.get(1, 1));
}
Esto se sintió extraño ya que estaba forzando el diseño de la implementación en función de cómo había decidido en esta etapa temprana escribir esta primera prueba.
En la forma en que entiendes TDD, ¿está bien? Parece que sigo los principios de TDD / XP en el sentido de que mis pruebas e implementación evolucionaron con el tiempo con la refactorización, por lo que si este diseño inicial hubiera resultado inútil, habría estado abierto a cambios, pero parece que estoy forzando una dirección sobre el solución comenzando de esta manera.
¿De qué otra forma las personas usan TDD? Podría haber pasado por más iteraciones de refactorización comenzando sin ningún objeto GameOfLife, solo primitivos y métodos estáticos, pero eso parece demasiado artificial.
fuente
Respuestas:
Creo que este es el punto clave en su pregunta, si esto es deseable o no depende de si se inclina hacia la idea de Codeninja de que debe diseñar por adelantado y luego usa TDD para completar la implementación, o la idea de Durron de que las pruebas deben participar eliminando el diseño y la implementación.
Creo que cuál de estos prefiere (o dónde cae en el medio) es algo que necesita descubrir por sí mismo como una preferencia. Es útil comprender los pros y los contras de cada enfoque. Probablemente hay muchos, pero diría que los principales son:
Diseño profesional por adelantado
Diseño de prueba de conducción profesional
Al construir su implementación alrededor de un cliente de su código (sus pruebas), obtiene la adherencia a YAGNI de forma gratuita, siempre y cuando no comience a escribir casos de prueba innecesarios. En términos más generales, obtienes una API que está diseñada en torno a su uso por parte de un consumidor, que en última instancia es lo que deseas.
La idea de dibujar un montón de diagramas UML antes de escribir cualquier código y luego completar los huecos es agradable, pero rara vez es realista. En el Código completo de Steve McConnell, el diseño se describe como un "problema perverso", un problema que no se puede entender completamente sin primero resolverlo al menos parcialmente. Combine esto con el hecho de que el problema subyacente en sí mismo puede cambiar a través de los requisitos cambiantes, y este modelo de diseño comienza a sentirse un poco desesperado. La prueba de manejo le permite comenzar una parte del trabajo a la vez, en el diseño, no solo en la implementación, y saber que, al menos durante la vida útil de cambiar de rojo a verde, esa tarea aún estará actualizada y será relevante.
En cuanto a su ejemplo en particular, como dice Durron, si optara por llevar a cabo el diseño escribiendo la prueba más simple, consumiendo la interfaz mínima posible, probablemente comenzaría con una interfaz más simple que la de su fragmento de código .
fuente
Para escribir la prueba en primer lugar, debe diseñar la API que luego va a implementar. Ya ha comenzado con el pie equivocado escribiendo su prueba para crear el objeto completo
GameOfLife
y usándola para implementar su prueba.De Pruebas unitarias prácticas con JUnit y Mockito :
Su prueba no intenta en gran medida diseñar una API. Ha configurado un sistema con estado donde toda la funcionalidad está contenida en el exterior
GameOfLife
clase .Si escribiera esta aplicación, pensaría en las piezas que quiero construir. Por ejemplo, podría hacer una
Cell
clase, escribir pruebas para eso, antes de pasar a la aplicación más grande. Ciertamente, crearía una clase para la estructura de datos "infinito en todas las direcciones" que se requiere para implementar adecuadamente Conway, y probarlo. Una vez hecho todo eso, pensaría en escribir la clase general que tiene unmain
método, etc.Es fácil pasar por alto el paso "escribir una prueba que falla". Pero escribir la prueba de falla que funciona de la manera que usted desea es el núcleo de TDD.
fuente
boolean
, ese diseño ciertamente sería peor para el rendimiento. ¿A menos que necesite ser extensible en el futuro a otros autómatas celulares con más de dos estados?Hay diferentes escuelas de pensamiento sobre esto.
Algunos dicen: la prueba no se compila es un error: vaya a arreglar escriba el código de producción más pequeño disponible.
Algunos dicen: está bien escribir la prueba primero, verificar si apesta (o no) y luego crear clases / métodos faltantes
Con el primer enfoque, realmente estás en un ciclo rojo-verde-refactorizador. Con el segundo, tiene una visión un poco más amplia de lo que desea lograr.
Depende de usted elegir la forma en que trabaja. En mi humilde opinión, ambos enfoques son válidos.
fuente
Incluso cuando implemente algo de forma "pirateada", sigo pensando en las clases y los pasos que estarán involucrados en todo el programa. Así que has pensado esto y escrito estos pensamientos de diseño como prueba primero, ¡eso es genial!
Ahora siga iterando a través de ambas implementaciones para completar esta prueba inicial, y luego agregue más pruebas para mejorar y extender el diseño.
Lo que podría ayudarlo es usar Pepino o similar para escribir sus pruebas.
fuente
Antes de comenzar a escribir sus pruebas, debe pensar en cómo diseñar su sistema. Debe pasar una cantidad considerable de tiempo durante su fase de diseño. Si lo hizo, no obtendrá esta confusión sobre TDD.
TDD es solo un enlace de enfoque de desarrollo : TDD
1. Agregue una prueba
2. Ejecute todas las pruebas y vea si la nueva falla
3. Escriba algún código
4. Ejecute pruebas
5. Código de refactorización
6. Repita
TDD lo ayuda a cubrir todas las características requeridas de lo que ha planeado antes de comenzar a desarrollar su software. enlace: Beneficios
fuente
No me gustan las pruebas de nivel de sistema escritas en Java o C # por esa razón. Mire SpecFlow para c # o uno de los marcos de prueba basados en Cucumber para java (quizás JBehave). Entonces sus pruebas pueden verse más así.
Y puede cambiar el diseño de su objeto sin tener que cambiar todas las pruebas de su sistema.
(Las pruebas unitarias "normales" son excelentes cuando se prueban clases individuales).
¿Cuáles son las diferencias entre los marcos BDD para Java?
fuente