TDD: ¿Lo estoy haciendo bien?

14

Soy un nuevo programador (solo he estado aprendiendo durante aproximadamente un año) y, en mi objetivo de mejorar, recientemente he aprendido sobre TDD. Quería adquirir el hábito de usarlo, ya que parece muy útil. Quería verificar y asegurarme de que lo estoy usando correctamente.

Que estoy haciendo:

  1. Piensa en un nuevo método que necesito.
  2. Crea una prueba para ese método.
  3. Prueba fallida
  4. Método de escritura.
  5. Pasar el examen.
  6. Método refactorizador.
  7. Repetir.

Estoy haciendo esto para CADA método que escribo, ¿hay algunos con los que no debería molestarme? Más tarde, generalmente pienso en una forma de probar mis métodos ya existentes de una manera o situación diferente. ¿Debo hacer estas nuevas pruebas en las que pienso, o ya que cada método ya tiene una prueba propia, no debería molestarme? ¿Puedo probar MÁS mi código? Supongo que es mi principal preocupación preguntar esto.

EDITAR

Además, esto era algo que me preguntaba. Al hacer algo como hacer una GUI, ¿sería necesario TDD en esa situación? Personalmente, no puedo pensar en cómo escribiría pruebas para eso.

cgasser
fuente
55
Ya lo estás haciendo mucho mejor que los profesionales experimentados que dicen que están probando todo (pero no lo hacen).
Yannis
Lo que usted describe no es el espíritu de TDD.
1
Es posible que desee ver ATDD o BDD.
dietbuddha
Quizás comience más alto: piense en un nuevo módulo que necesite.

Respuestas:

16

Lo que está describiendo como un flujo de trabajo no es, en mi opinión, el espíritu de TDD.

La sinopsis del libro de Kent Becks en Amazon dice:

En pocas palabras, el desarrollo basado en pruebas está destinado a eliminar el miedo en el desarrollo de aplicaciones.Si bien algo de miedo es saludable (a menudo visto como una conciencia que le dice a los programadores que "tengan cuidado"), el autor cree que los subproductos del miedo incluyen programadores tentativos, gruñones y poco comunicativos que no pueden absorber las críticas constructivas. Cuando los equipos de programación compran TDD, inmediatamente ven resultados positivos. Eliminan el miedo involucrado en sus trabajos y están mejor equipados para enfrentar los desafíos difíciles que enfrentan. TDD elimina los rasgos tentativos, enseña a los programadores a comunicarse y alienta a los miembros del equipo a buscar críticas. Sin embargo, ¡incluso el autor admite que el mal humor debe resolverse individualmente! En resumen, la premisa detrás de TDD es que el código debe ser continuamente probado y refactorizado.

TDD práctico

Pruebas formales automatizadas, especialmente Pruebas unitarias, cada método de cada clase es tan malo como un antipatrón y no prueba nada. Hay un equilibrio para tener. ¿Estás escribiendo pruebas unitarias para cada setXXX/getXXXmétodo, también son métodos!

Además, las pruebas pueden ayudar a ahorrar tiempo y dinero, pero no olvide que cuestan tiempo y dinero desarrollarlas y que son código, por lo que cuestan tiempo y dinero mantenerlas. Si se atrofian por falta de mantenimiento, se convierten en una responsabilidad más que un beneficio.

Como todo esto, hay un equilibrio que no puede ser definido por nadie más que por ti mismo. Cualquier dogma de cualquier manera es probablemente más incorrecto que correcto.

Una buena métrica es el código que es crítico para la lógica del negocio y está sujeto a modificaciones frecuentes basadas en los requisitos cambiantes. Esas cosas necesitan pruebas formales que estén automatizadas, eso sería un gran retorno de la inversión.

Va a ser muy difícil encontrar muchas tiendas profesionales que funcionen de esta manera. Simplemente no tiene sentido comercial gastar dinero probando cosas que, para todos los fines prácticos, nunca cambiarán después de realizar una simple prueba de humo. Escribir pruebas unitarias automatizadas formales para .getXXX/.setXXXmétodos es un excelente ejemplo de esto, una completa pérdida de tiempo.

Han pasado dos décadas desde que se señaló que las pruebas del programa pueden demostrar de manera convincente la presencia de errores, pero nunca pueden demostrar su ausencia. Después de citar devotamente este comentario bien publicitado, el ingeniero de software vuelve al orden del día y continúa refinando sus estrategias de prueba, al igual que el alquimista de antaño, que continuó refinando sus purificaciones crioscósmicas.

- Edsger W. Djikstra . (Escrito en 1988, ahora está más cerca de 4.5 décadas).

Ver también esta respuesta .

Comunidad
fuente
1
Eso prácticamente aborda lo que me preocupaba. Sentía que no debería probar todos los métodos como lo estaba haciendo, pero no estaba seguro. Parece que todavía necesito leer un poco más sobre TDD.
cgasser
@kevincline La mayoría de las veces setXXX/getXXXno son necesarias :)
Chip
1
Cuando recuerdas ese getXXX trivial y lo haces mal, o introduces una carga lenta en tu getXXX y lo haces mal, entonces sabrás que a veces realmente quieres probar tus captadores.
Frank Shearar
13

Estas muy cerca. Intenta pensar de esta manera ligeramente diferente.

  1. Piensa en un nuevo comportamiento que necesito.
  2. Crea una prueba para ese comportamiento.
  3. Prueba fallida
  4. Escribir nuevo o ampliar el método existente.
  5. Pasar el examen.
  6. Código de refactorización.
  7. Repetir.

No cree automáticamente getters y setters para cada propiedad . No piense en un método completo y escriba las pruebas para cubrir toda la funcionalidad . Intente encapsular las propiedades dentro de la clase y escriba métodos para proporcionar el comportamiento que necesita. Deje que sus métodos evolucionen hacia un buen diseño en lugar de tratar de planificarlos por adelantado. Recuerde que TDD es un proceso de diseño, no un proceso de prueba. La ventaja que tiene sobre otros procesos de diseño es que deja atrás un flujo de pruebas de regresión automatizadas, en lugar de un pedazo de papel que arroje a la basura.

Además, recuerde las tres reglas de TDD del tío Bob .

  1. No está permitido escribir ningún código de producción a menos que sea para aprobar una prueba de unidad que falla.
  2. No se le permite escribir más de una prueba unitaria de la que es suficiente para fallar; y las fallas de compilación son fallas.
  3. No se le permite escribir más código de producción del que sea suficiente para pasar la prueba de la unidad que falla.
pdr
fuente
1
@Zexanima: Lo estás haciendo mucho mejor que la mayoría de nosotros después de un año. Solo trato de señalar el siguiente paso.
pdr
2
Creo que estas 3 reglas las enlazas; Por más idílico que pueda parecer, son excepcionalmente dogmáticos y muy poco realistas rígidos en el 99% de todas las tiendas de producción que cualquiera encontrará.
1
@FrankShearar o se puede ver como el parloteo impráctico de un extremista fundamentalista y por completo ignorado. He trabajado en tiendas que tenían esta actitud dogmática, tomaron el dogma literalmente y perdieron el punto; escribiendo pruebas que no probaron ninguno de sus códigos reales de manera práctica y terminando simplemente probando la capacidad de los marcos de inyección de burla y dependencia para confundir lo que era importante en el mejor de los casos.
1
@pdr El espíritu de algo se opone diametralmente a la canonización dogmática formalizada de esa cosa. Una cosa es tener una filosofía y otra es convertirla en una religión . TDD se habla más de las veces en términos religiosos dogmáticos en blanco y negro . Estas 3 reglas suenan dogmáticas y religiosas en la presentación y lo que se escucha es el Test, Test, Test mantra, para alguien como el OP, lo toman literalmente y eso causa más daño que bien. Le respondí a Frank que las declaraciones polarizantes pueden hacer más daño a la causa que bien.
2
Mi punto era que el dogmatismo proviene de aceptar ciegamente algo como evangelio . Toma la declaración polarizante, pruébalo, haz que te obligue a salir de tu zona de confort. No puede evaluar las compensaciones involucradas en TDD si no prueba el enfoque extremo de 3 puntos de todo o nada, porque no tendrá datos .
Frank Shearar
5

Pocas cosas para agregar a las respuestas de otros:

  1. Existe una prueba excesiva. Desea asegurarse de que las pruebas de su unidad se superpongan lo menos posible. No tiene sentido que múltiples pruebas verifiquen las mismas condiciones en el mismo fragmento de código. Por otro lado, cuando refactorice su código de producción y tenga muchas pruebas que se superpongan a esa sección, tendrá que regresar y corregir todas esas pruebas. Mientras que si no se superponen, un cambio interrumpirá a lo sumo una sola prueba.

  2. Solo porque pensaste en una mejor manera de escribir un examen, no volvería allí y comenzaría a reescribirlo. Esto se remonta a las personas que siguen escribiendo y reescribiendo la misma clase / función porque intentan que sea perfecta. Nunca será perfecto, así que sigue adelante. Cuando descubra un método mejor, manténgalo en su mente (o agréguelo a los comentarios de la prueba). La próxima vez que esté allí, y vea un beneficio inmediato de cambiar a la nueva forma, ese es el momento de refactorizar. De lo contrario, si la función está terminada y usted siguió adelante y todo funciona, déjelo funcionando.

  3. TDD se enfoca en entregar valor inmediato, no simplemente en asegurarse de que cada función sea comprobable. Cuando agregue funcionalidad, comience preguntando "qué necesita el cliente". Luego defina una interfaz para darle al cliente lo que necesita. Luego implemente lo que sea necesario para que la prueba pase. TDD es casi como probar escenarios de casos de uso (incluidos todos los "qué pasa si"), en lugar de simplemente codificar funciones públicas y probar cada una.

  4. Preguntaste sobre probar el código GUI. Busque los patrones "Diálogo humilde" y "MVVM". La idea detrás de ambos es crear un conjunto de clases de "modelo de vista", que en realidad no tienen una lógica específica de UI. Sin embargo, estas clases tendrán toda la lógica de negocios que generalmente forma parte de su interfaz de usuario y estas clases deben ser 100% comprobables. Lo que queda es un shell de interfaz de usuario muy delgado y sí, generalmente ese shell se queda sin cobertura de prueba, pero en ese punto casi no debería tener lógica.

  5. Si tiene una gran parte del código existente, como pocos sugirieron, no debe comenzar a agregar pruebas unitarias absolutamente en todas partes. Te llevará una eternidad y no obtendrás beneficios al agregar pruebas unitarias al 80% de las clases que son estables y no cambiarán en el futuro cercano (o no tan cercano). Sin embargo, para un nuevo trabajo, encuentro que usar el desarrollo TDD con TODO el código es extremadamente beneficioso. No solo terminas con una suite con pruebas automatizadas cuando terminas, sino que el desarrollo real tiene enormes beneficios:

    • Al considerar la capacidad de prueba, escribirá código que está menos acoplado y es más modular
    • Al considerar su contrato público antes que nada, terminará con interfaces públicas que son mucho más limpias
    • Mientras escribe el código, verificar la nueva funcionalidad lleva milisegundos en comparación con ejecutar toda la aplicación e intentar forzar la ejecución por el camino correcto. Mi equipo aún libera código de manejo de errores que ni siquiera se ejecutó UNA VEZ solo porque no pudieron obtener el conjunto correcto de condiciones. Es sorprendente cuánto tiempo perdemos cuando más tarde en QA esas condiciones suceden. Y sí, gran parte de este código es lo que alguien habría considerado "no será un área de muchos cambios en el futuro una vez que se realicen las pruebas de humo".
DXM
fuente
1

Hay algunos métodos que no se están probando, a saber, esas pruebas. Sin embargo, hay algo que decir sobre algunas pruebas que se agregan después de que se ha escrito el código inicial, como las condiciones de contorno y otros valores, de modo que puede haber múltiples pruebas en un solo método.

Si bien puede probar en exceso su código, eso generalmente ocurre cuando alguien quiere probar cada posible permutación de entradas que no suena como lo que está haciendo. Por ejemplo, si tiene un método que toma un carácter, ¿escribe una prueba para cada valor posible que pueda ingresarse? Ahí sería donde tendrías una sobreevaluación, OMI.

JB King
fuente
Ah, está bien. Eso no es lo que estoy haciendo. Por lo general, termino pensando en una situación diferente en la que podría probar mis métodos en el futuro después de que ya haya realizado su prueba inicial. Solo me estaba asegurando de que valía la pena hacer esas pruebas 'adicionales', o si había terminado de hacerlo.
cgasser
Si trabaja en incrementos lo suficientemente pequeños, generalmente puede estar razonablemente seguro de que su prueba realmente funciona. En otras palabras, tener una prueba fallida (¡por la razón correcta!) Es en sí mismo probar la prueba. Pero ese nivel de "razonablemente seguro" no será tan alto como el código bajo prueba.
Frank Shearar
1

Generalmente lo estás haciendo bien.

Las pruebas son código. Entonces, si puede mejorar la prueba, continúe y refactorícela. Si cree que se puede mejorar una prueba, continúe y cámbiela. No tenga miedo de reemplazar una prueba con una mejor.

Recomiendo al probar su código, evite especificar cómo se supone que el código debe hacer lo que está haciendo. Las pruebas deben observar los resultados de los métodos. Esto ayudará con la refactorización. Algunos métodos no necesitan ser probados explícitamente (es decir, getters y setters simples) porque los usará para verificar los resultados de otras pruebas.

Schleis
fuente
Estaba escribiendo pruebas para getters y setters también, así que gracias por ese consejo. Eso me ahorrará un trabajo innecesario.
cgasser
"Algunos métodos no necesitan ser probados explícitamente (es decir, captadores y definidores simples)" - ¿Nunca ha copiado / pegado un captador y definidor y se olvidó de cambiar el nombre del campo detrás de él? Lo que pasa con el código simple es que requiere pruebas simples: ¿cuánto tiempo realmente está ahorrando?
pdr
No quiero decir que el método no haya sido probado. Simplemente se verifica confirmando que se han establecido otros métodos o durante la configuración real de una prueba. Si el captador o definidor no funciona correctamente, la prueba fallará porque las propiedades no se configuraron correctamente. Los haces probar gratis, implícitamente.
Schleis
Las pruebas de getter y setter no toman mucho tiempo, por lo que probablemente continuaré haciéndolas. Sin embargo, nunca copio y pego ninguno de mis códigos, así que no me encuentro con ese problema.
cgasser
0

Mi opinión sobre TDD es que las herramientas han creado un mundo de desarrolladores de estilo 'apuntar y hacer clic'. El hecho de que las herramientas creen un trozo de prueba para cada método no significa que deba escribir pruebas para cada método. Algunas personas están 'renombrando' TDD como BDD (desarrollo impulsado por el comportamiento) donde las pruebas son mucho más amplias y tienen la intención de probar el comportamiento de la clase, no cada método poco complicado.

Si diseña sus pruebas para evaluar la clase como está destinada a ser utilizada, entonces comienza a obtener algunos beneficios, especialmente a medida que comienza a escribir pruebas que ejercen un poco más que cada método, especialmente cuando comienza a probar la interacción de esos métodos. Supongo que podrías pensar en ello como escribir pruebas para una clase, en lugar de métodos. En cualquier caso, aún debe escribir 'pruebas de aceptación' que ejerciten la combinación de métodos para asegurarse de que no haya contradicciones o conflictos en la forma en que se usan juntos.

No confunda TDD con las pruebas, no lo es. TDD está diseñado para que escriba código para ejercer sus requisitos, no para probar los métodos. Es un punto sutil pero importante que a menudo se pierde en las personas que escriben ciegamente códigos de prueba para cada método. Es que debes escribir pruebas que aseguren que tu código haga lo que quieres que haga, no que el código que escribiste funcione como se supone que debe hacerlo.

Hay algunos buenos enlaces a la derecha sobre BDD v TDD. Échales un vistazo.

gbjbaanb
fuente
0

Cuando comienza a aprender TDD, sí, debe seguir ciegamente el enfoque dogmático de no escribir una sola línea de código, excepto para aprobar una prueba reprobatoria y escribir solo una prueba suficiente para fallar (y fallar por la razón correcta / esperada) .

Una vez que haya aprendido de qué se trata TDD, ENTONCES puede decidir que ciertos tipos de cosas no valen la pena probar. Este es el mismo enfoque que debes seguir para todo, y las artes marciales japonesas llaman a esto " shuhari ". (El enlace también explica cómo uno puede progresar a través de las etapas de aprendizaje sin un maestro, que es, sospecho, cómo la mayoría de la gente tiene que aprender).

Frank Shearar
fuente
0

Creo que estás exagerando.

He practicado TDD durante muchos años y, en mi experiencia, cuando TDD se realiza de manera efectiva, obtienes dos beneficios principales:

  • Proporcionar retroalimentación rápida
  • Habilitar refactorización

Proporcionar retroalimentación rápida

Particularmente con lenguajes dinámicos, puedo ejecutar las pruebas relevantes en menos de un segundo. Y tengo observadores del sistema de archivos que ejecutan estas pruebas automáticamente cuando se cambia un archivo fuente en el disco. Por lo tanto, prácticamente no tengo tiempo de espera para las pruebas, e inmediatamente sé si el código que escribí hizo lo esperado. Por lo tanto, TDD conduce a una forma de trabajo muy eficiente.

Habilitar refactorización

Si tiene un buen conjunto de pruebas, puede refactorizar de forma segura, a medida que obtiene nuevos conocimientos sobre cómo debe diseñarse el sistema.

Un buen conjunto de pruebas le permite trasladar la responsabilidad en su código, y aún tener la confianza de que el código funciona como se espera después del traslado. Y debería poder hacer esto con pequeños cambios en el código de prueba.

Si escribe pruebas para cada método en su sistema, lo más probable es que no pueda refactorizar fácilmente su código, cada refactorizador de su código requerirá cambios masivos en el código de prueba. ¿Y puede estar seguro de que el código de prueba todavía funciona como se esperaba? ¿O introdujo accidentalmente un error en el código de prueba, que en consecuencia conduce a un error en el código de producción?

Sin embargo, si, como también se sugiere en la respuesta de pdr , se concentra en el comportamiento en lugar de los métodos al escribir pruebas, tendrá pruebas que requerirán muchos menos cambios al refactorizar el sistema.

O como dice Ian Cooper en esta presentación (cité de memoria, por lo que podría no estar correctamente citado):

Su razón para escribir una nueva prueba debe ser agregar un nuevo comportamiento, no agregar una nueva clase

Pete
fuente
-2

Debes probar todos los métodos públicos .

El problema aquí es que si sus métodos públicos son muy pequeños, probablemente exponga demasiada información. La práctica común de exponer cada propiedad como getXXX()realmente rompe la encapsulación.

Si sus métodos públicos son realmente el comportamiento de la clase, entonces debe probarlos. Si no, no son buenos métodos públicos.

EDITAR: la respuesta de pdr es mucho más completa que la mía.

Chip
fuente