Todos los ejemplos que he leído y visto en videos de capacitación tienen ejemplos simplistas. Pero lo que no veo es cómo hago el código "real" después de que me ponga verde. ¿Es esta la parte "Refactor"?
Si tengo un objeto bastante complejo con un método complejo, y escribo mi prueba y el mínimo necesario para que pase (después de que falla por primera vez, Rojo). ¿Cuándo regreso y escribo el código real? ¿Y cuánto código real escribo antes de volver a probar? Supongo que la última es más intuición.
Editar: Gracias a todos los que respondieron. Todas sus respuestas me ayudaron inmensamente. Parece que hay diferentes ideas sobre lo que estaba preguntando o confundido, y tal vez sí, pero lo que estaba preguntando era, digamos que tengo una solicitud para construir una escuela.
En mi diseño, tengo una arquitectura con la que quiero comenzar, Historias de usuarios, etc. A partir de aquí, tomo esas historias de usuario y creo una prueba para probar la historia de usuario. El usuario dice: "Tenemos personas que se matriculan en la escuela y pagan tarifas de inscripción" Entonces, pienso en una manera de hacer que eso falle. Al hacerlo, diseño una clase de prueba para la clase X (tal vez Estudiante), que fallará. Luego creo la clase "Estudiante". Tal vez "Escuela" no lo sé.
Pero, en cualquier caso, el diseño de TD me está obligando a pensar en la historia. Si puedo hacer que una prueba falle, sé por qué falla, pero esto presupone que puedo aprobarla. Se trata del diseño.
Comparo esto con pensar en la recursión. La recursión no es un concepto difícil. Puede ser más difícil seguirlo en la cabeza, pero en realidad, lo más difícil es saber cuándo "se rompe" la recursión, cuándo parar (mi opinión, por supuesto). Así que tengo que pensar qué se detiene. La recursividad primero. Es solo una analogía imperfecta, y supone que cada iteración recursiva es un "paso". De nuevo, solo una opinión.
En la implementación, la escuela es más difícil de ver. Los libros de contabilidad numéricos y bancarios son "fáciles" en el sentido de que puede usar aritmética simple. Puedo ver a + b y devolver 0, etc. En el caso de un sistema de personas, tengo que pensar más en cómo implementar eso. Tengo el concepto de reprobar, aprobar, refactorizar (principalmente debido al estudio y esta pregunta).
Lo que no sé se basa en la falta de experiencia, en mi opinión. No sé cómo dejar de inscribir a un nuevo estudiante. No sé cómo fallarle a alguien que escribe un apellido y se guarda en una base de datos. Sé cómo hacer un + 1 para matemáticas simples, pero con entidades como una persona, no sé si solo estoy probando para ver si recupero una identificación única de la base de datos o algo más cuando alguien ingresa un nombre en un base de datos o ambos o ninguno.
O, tal vez esto muestra que todavía estoy confundido.
Respuestas:
No "regresas" y escribes "código real". Todo es código real. Lo que debe hacer es regresar y agregar otra prueba que lo obligue a cambiar su código para poder aprobar la nueva prueba.
¿En cuanto a cuánto código escribes antes de volver a probar? Ninguna. Escribe un código cero sin una prueba fallida que lo obliga a escribir más código.
¿Ves el patrón?
Veamos (otro) ejemplo simple con la esperanza de que ayude.
Fácil peazy.
No es lo que llamarías código real, ¿verdad? Agreguemos una prueba que fuerce un cambio.
Podríamos hacer algo tonto
if n == 1
, pero pasaremos a la solución sensata.Bueno. Esto funcionará para todos los números que no sean FizzBuzz. ¿Cuál es la siguiente entrada que obligará a cambiar el código de producción?
Y otra vez. Escribe una prueba que aún no pase.
Y ahora hemos cubierto todos los múltiplos de tres (que no son también múltiplos de cinco, lo notaremos y volveremos).
Todavía no hemos escrito una prueba para "Buzz", así que escribamos eso.
Y nuevamente, sabemos que hay otro caso que debemos manejar.
Y ahora podemos manejar todos los múltiplos de 5 que no son también múltiplos de 3.
Hasta este momento, hemos estado ignorando el paso de refactorización, pero veo algunas duplicaciones. Vamos a limpiar eso ahora.
Bueno. Ahora hemos eliminado la duplicación y creado una función bien nombrada. ¿Cuál es la próxima prueba que podemos escribir que nos obligará a cambiar el código? Bueno, hemos estado evitando el caso donde el número es divisible entre 3 y 5. Escribámoslo ahora.
Las pruebas pasan, pero tenemos más duplicaciones. Tenemos opciones, pero voy a aplicar "Extraer variable local" varias veces para que reescribamos en lugar de reescribir.
Y hemos cubierto todos los aportes razonables, pero ¿qué pasa con los aportes irrazonables ? ¿Qué sucede si pasamos 0 o un negativo? Escribe esos casos de prueba.
¿Esto ya empieza a parecerse a "código real"? Más importante aún, ¿en qué punto dejó de ser "código irreal" y pasó a ser "real"? Eso es algo para reflexionar ...
Entonces, pude hacer esto simplemente buscando una prueba que sabía que no pasaría en cada paso, pero he tenido mucha práctica. Cuando estoy en el trabajo, las cosas nunca son tan simples y no siempre sé qué prueba forzará un cambio. ¡A veces escribo una prueba y me sorprende ver que ya pasa! Le recomiendo que tenga el hábito de crear una "Lista de prueba" antes de comenzar. Esta lista de prueba debe contener todas las entradas "interesantes" que se te ocurran. Es posible que no los use todos y probablemente agregará casos a medida que avanza, pero esta lista sirve como una hoja de ruta. Mi lista de pruebas para FizzBuzz se vería así.
fuente
El código "real" es el código que escribe para que su prueba pase. De verdad . Es así de simple.
Cuando la gente habla de escribir lo mínimo para hacer que la prueba sea verde, eso solo significa que su código real debe seguir el principio YAGNI .
La idea del paso de refactorización es simplemente limpiar lo que ha escrito una vez que esté satisfecho de que cumple con los requisitos.
Siempre que las pruebas que escriba abarquen los requisitos de su producto, una vez que pasen, el código estará completo. Piénselo, si todos sus requisitos comerciales tienen una prueba y todas esas pruebas son ecológicas, ¿qué más hay para escribir? (De acuerdo, en la vida real no tendemos a tener una cobertura de prueba completa, pero la teoría es sólida).
fuente
switch
con un caso para cada prueba unitaria que pasaría todas las pruebas y fallaría para cualquier otra entrada.switch
" tenía la intención de ser un extremo lógico de "escribir lo mínimo para hacer las pruebas verdes". Veo la pregunta del OP como: ¿en qué parte de TDD está el principio que evita estoswitch
?La respuesta corta es que el "código real" es el código que hace pasar la prueba. Si puede hacer que su prueba pase con algo que no sea código real, ¡agregue más pruebas!
Estoy de acuerdo en que muchos tutoriales sobre TDD son simplistas. Eso va en contra de ellos. Una prueba demasiado simple para un método que, por ejemplo, calcula 3 + 8 realmente no tiene más remedio que calcular también 3 + 8 y comparar el resultado. Eso hace que parezca que solo va a duplicar el código, y que las pruebas son un trabajo adicional inútil y propenso a errores.
Cuando sea bueno en las pruebas, eso informará cómo estructura su aplicación y cómo escribe su código. Si tiene problemas para realizar pruebas sensatas y útiles, probablemente debería repensar un poco su diseño. Un sistema bien diseñado es fácil de probar, lo que significa que las pruebas sensatas son fáciles de pensar e implementar.
Cuando escriba sus pruebas primero, mírelas fallar y luego escriba el código que las hace pasar, eso es una disciplina para garantizar que todo su código tenga las pruebas correspondientes. No sigo servilmente esa regla cuando estoy codificando; a menudo escribo pruebas después del hecho. Pero hacer las pruebas primero te ayuda a ser honesto. Con un poco de experiencia, comenzará a notar cuándo se está codificando en una esquina, incluso cuando no está escribiendo primero las pruebas.
fuente
assertEqual(plus(3,8), 11)
, noassertEqual(plus(3,8), my_test_implementation_of_addition(3,8))
. Para casos más complejos, siempre busca una forma de probar el resultado correcto, que no sea calcular dinámicamente el resultado correcto en la prueba y verificar la igualdad.plus(3,8)
ha devuelto el resultado correcto restando 3, restando 8 de eso y verificando el resultado contra 0. Esto es tan obviamente equivalenteassertEqual(plus(3,8), 3+8)
a ser un es un poco absurdo, pero si el código bajo prueba está creando algo más complicado que solo un número entero, entonces tomar el resultado y verificar que cada parte sea correcta suele ser el enfoque correcto. Alternativamente, algo así comofor (i=0, j=10; i < 10; ++i, ++j) assertEqual(plus(i, 10), j)
plus()
puede agregar 10 a las cosas. Todavía confiamos en los valores de bucle inicial verificados por el programador, por supuesto.A veces, algunos ejemplos sobre TDD pueden ser engañosos. Como otras personas han señalado antes, el código que usted escribe para pasar las pruebas es el código real.
Pero no piense que el código real parece mágico, eso está mal. Necesita una mejor comprensión de lo que desea lograr y luego debe elegir la prueba en consecuencia, comenzando por los casos más fáciles y los casos de esquina.
Por ejemplo, si necesita escribir un lexer, comienza con una cadena vacía, luego con un montón de espacios en blanco, luego un número, luego con un número rodeado de espacios en blanco, luego un número incorrecto, etc. Estas pequeñas transformaciones lo llevarán a el algoritmo correcto, pero no saltas del caso más fácil a un caso altamente complejo elegido tontamente para obtener el código real.
Bob Martin lo explica perfectamente aquí .
fuente
La parte del refactorizador se limpia cuando estás cansado y quieres ir a casa.
Cuando esté a punto de agregar una función, la parte del refactorizador es lo que cambia antes de la próxima prueba. Refactoriza el código para dejar espacio para la nueva característica. Hace esto cuando sabe cuál será esa nueva característica. No cuando lo estás imaginando.
Esto puede ser tan simple como cambiar el nombre
GreetImpl
deGreetWorld
antes de crear unaGreetMom
clase (después de la adición de una prueba) para añadir una característica que debe imprimir "Hola mamá".fuente
Pero el código real aparecería en la etapa de refactorización de la fase TDD. Es decir, el código que debería ser parte de la versión final.
Las pruebas deben ejecutarse cada vez que realice un cambio.
El lema del ciclo de vida de TDD sería: REFACTOR VERDE ROJO
ROJO : Escribe las pruebas
VERDE : Haga un intento honesto de obtener código funcional que pase las pruebas lo más rápido posible: código duplicado, hacks de variables de nombre oscuro del orden más alto, etc.
REFACTOR : Limpia el código, nombra correctamente las variables. SECAR el código.
fuente
La fase roja es donde escribes el código.
En la fase de refactorización , el objetivo principal es eliminar el código.
En la fase roja , debe hacer todo lo posible para que la prueba pase lo más rápido posible y a cualquier costo . Ignora por completo lo que ha escuchado sobre buenas prácticas de codificación o patrones de diseño similares. Hacer que la prueba sea verde es todo lo que importa.
En la fase de refactorización , limpia el desorden que acaba de hacer. Ahora observa primero si el cambio que acabas de hacer es el que está más arriba en la lista de Prioridad de transformación y si hay alguna duplicación de código que probablemente puedas eliminar aplicando un patrón de diseño.
Finalmente, mejora la legibilidad al renombrar identificadores y extraer números mágicos y / o cadenas literales a constantes.
Gracias por señalar esto.
Entonces es la fase verde donde escribes el código real
En la fase roja , escribe la especificación ejecutable ...
fuente
Estás escribiendo Código Real todo el tiempo.
En cada paso, está escribiendo un código para satisfacer las condiciones que su código satisfará para las futuras llamadas de su código (que podría ser usted o no ...).
Cree que no está escribiendo código útil ( real ), porque en un momento podría refactorizarlo.
Lo que esto significa es que a pesar de que está cambiando el código, las condiciones que el código cumple no se modifican. Y las verificaciones ( pruebas ) que implementó para verificar su código ya están allí para verificar si sus modificaciones cambiaron algo. Entonces, el código que escribió todo el tiempo está allí, solo que de una manera diferente.
Otra razón por la que podría pensar que no es un código real, es que está haciendo ejemplos en los que el programa final ya puede ser visto por usted. Esto es muy bueno, ya que muestra que tienes conocimiento sobre el dominio en el que estás programando.
Pero muchas veces los programadores están en un dominio que es nuevo , desconocido para ellos. No saben cuál será el resultado final y TDD es una técnica para escribir programas paso a paso, documentando nuestro conocimiento sobre cómo debería funcionar este sistema y verificando que nuestro código funcione de esa manera.
Cuando leí The Book (*) en TDD, para mí, la característica más importante que se destacó fue: la lista TODO. Me demostró que, TDD también es una técnica para ayudar a los desarrolladores a centrarse en una cosa a la vez. Entonces, esta también es una respuesta a su pregunta sobre ¿Cuánto código real escribir ? Yo diría suficiente código para enfocarme en 1 cosa a la vez.
(*) "Test Driven Development: By Example" de Kent Beck
fuente
No estás escribiendo código para hacer que tus pruebas fallen.
Escribes tus pruebas para definir cómo debería ser el éxito, que inicialmente debería fallar porque aún no has escrito el código que pasará.
El objetivo principal de escribir pruebas inicialmente fallidas es hacer dos cosas:
El punto detrás de red-green-refactor es que escribir primero las pruebas correctas le da la confianza de saber que el código que escribió para aprobar las pruebas es correcto y le permite refactorizar con la confianza de que sus pruebas le informarán tan pronto algo se rompe, por lo que puede volver inmediatamente y arreglarlo.
En mi propia experiencia (C # / .NET), la prueba pura primero es un poco un ideal inalcanzable, porque no puede compilar una llamada a un método que aún no existe. Por lo tanto, "probar primero" se trata realmente de codificar las interfaces y las implementaciones de stubbing primero, luego escribir las pruebas contra los stubs (que inicialmente fallarán) hasta que los stubs estén debidamente desarrollados. Nunca escribo "código defectuoso", solo construyo desde trozos.
fuente
Creo que puede confundirse entre las pruebas unitarias y las pruebas de integración. Creo que también puede haber pruebas de aceptación, pero eso depende de su proceso.
Una vez que ha probado todas las pequeñas "unidades", entonces las prueba todas ensambladas o "integradas". Eso suele ser un programa completo o una biblioteca.
En el código que he escrito, la integración prueba una biblioteca con varios programas de prueba que leen datos y los envían a la biblioteca, luego verifican los resultados. Luego lo hago con hilos. Luego lo hago con hilos y tenedor () en el medio. Luego lo ejecuto y mato -9 después de 2 segundos, luego lo inicio y verifico su modo de recuperación. Lo fumo. Lo torturo de muchas maneras.
Todo eso también está probando, pero no tengo una pantalla roja / verde para los resultados. O tiene éxito, o busco algunos miles de líneas de código de error para averiguar por qué.
Ahí es donde se prueba el "código real".
Y solo pensé en esto, pero tal vez no sabes cuándo se supone que debes terminar de escribir pruebas unitarias. Ha terminado de escribir pruebas unitarias cuando sus pruebas ejercitan todo lo que especificó que debería hacer. A veces, puede perder el rastro de eso entre todos los casos de manejo de errores y casos límite, por lo que es posible que desee hacer un buen grupo de pruebas de pruebas de ruta feliz que simplemente pasen directamente por las especificaciones.
fuente
En respuesta al título de la pregunta: "¿Cuándo escribes el código" real "en TDD?", La respuesta es: "casi nunca" o "muy lentamente".
Suenas como un estudiante, entonces responderé como si estuvieras aconsejando a un estudiante.
Aprenderá muchas 'teorías' y 'técnicas' de codificación. Son excelentes para pasar el tiempo en cursos de estudiantes caros, pero de muy poco beneficio para usted que no podría leer en un libro en la mitad del tiempo.
El trabajo de un codificador es únicamente producir código. Código que funciona muy bien. Es por eso que usted, el codificador, planifica el código en su mente, en papel, en una aplicación adecuada, etc., y planea solucionar posibles fallas / agujeros de antemano pensando lógicamente y lateralmente antes de la codificación.
Pero necesita saber cómo romper su aplicación para poder diseñar un código decente. Por ejemplo, si no conocía Little Bobby Table (xkcd 327), entonces probablemente no estaría desinfectando sus entradas antes de trabajar con la base de datos, por lo que no podría proteger sus datos en torno a ese concepto.
TDD es solo un flujo de trabajo diseñado para minimizar los errores en su código al crear las pruebas de lo que podría salir mal antes de codificar su aplicación porque la codificación puede ser exponencialmente difícil cuanto más código introduzca y olvide los errores que alguna vez pensó. Una vez que crees que has terminado tu aplicación, ejecutas las pruebas y el boom, con suerte se detectan errores con tus pruebas.
TDD no es, como algunas personas creen, escribir una prueba, pasarla con un código mínimo, escribir otra prueba, pasarla con un código mínimo, etc. En cambio, es una forma de ayudarlo a codificar con confianza. Este ideal de código de refactorización continua para que funcione con las pruebas es idiota, pero es un buen concepto entre los estudiantes porque los hace sentir bien cuando agregan una nueva función y todavía están aprendiendo a codificar ...
No caiga en esta trampa y vea su función de codificación de lo que es: el trabajo de un codificador es únicamente producir código. Código que funciona muy bien. Ahora, recuerde que estará en el reloj como codificador profesional, y a su cliente no le importará si escribió 100,000 afirmaciones, o 0. Solo quieren un código que funcione. Muy bien, de hecho.
fuente