¿Es una buena idea escribir todos los casos de prueba posibles después de transformar el equipo a TDD para lograr una cobertura completa?

17

Supongamos que tenemos una gran aplicación de nivel empresarial sin pruebas unitarias / funcionales. No hubo un proceso de desarrollo basado en pruebas durante el desarrollo debido a plazos muy ajustados (sé que nunca deberíamos prometer plazos estrictos cuando no estamos seguros, ¡pero lo hecho, hecho está!)

Ahora que todos los plazos pasaron y las cosas están tranquilas, todos acordaron transformarnos en un equipo productivo basado en TDD / BDD ... ¡Yay!

Ahora la pregunta es sobre el código que ya tenemos: (1) ¿Está bien o es una buena idea detener la mayor parte del desarrollo y comenzar a escribir todos los casos de prueba posibles desde el principio, a pesar de que todo funciona completamente BIEN (todavía) ? O (2) es mejor esperar a que ocurra algo malo y luego, durante la reparación, escribir nuevas pruebas unitarias, o (3) incluso olvidarse de los códigos anteriores y simplemente escribir pruebas unitarias solo para los nuevos códigos y posponer todo al siguiente refactor principal.

Hay algunos artículos buenos y relacionados como este . Todavía no estoy seguro de si vale la pena invertir en esto teniendo en cuenta que tenemos un tiempo muy limitado y que muchos otros proyectos / trabajos nos están esperando.

Nota : Esta pregunta explica / imagina una situación totalmente incómoda en un equipo de desarrollo. No se trata de mí ni de ninguno de mis colegas; Es solo una situación imaginaria. ¡Puede pensar que esto nunca debería suceder o que el gerente de desarrollo es responsable de tal desorden! Pero de todos modos, lo hecho, hecho está. Si es posible, por favor no haga un voto negativo solo porque cree que esto nunca debería suceder.

Michel Gokan
fuente
66
Probablemente debería estar preparándose para la próxima vez que lleguen las fechas límite y ya no se le permite hacer TDD. Posiblemente al decirle a quien condujo la última ronda de desarrollo de la deuda técnica por qué no fue una gran idea.
jonrsharpe
1
@gnat Creo que no es una pregunta duplicada. El equipo mencionado no tiene ningún tipo de pruebas (ni siquiera pruebas de integración)
Michel Gokan
1
@gnat las preguntas son: ¿qué pasará con nuestras nuevas pruebas unitarias? Pueden parecer incompletos, o incluso inútiles sin escribir todas las pruebas unitarias para los códigos escritos previamente. La pregunta que menciona no cubre esta preocupación específica.
Michel Gokan
1
No es posible escribir todos los casos de prueba posibles. Solo es útil escribir todos los casos de prueba que le interesan. Por ejemplo, si necesita una función que acepte un intvalor y devuelva algo específico, no es posible escribir una prueba unitaria para cada intvalor posible , pero probablemente tenga sentido probar un puñado de valores útiles que podrían triplicar código, como números negativos (incluidos minint), cero maxint, etc. para asegurarse de que algunos casos límite estén cubiertos.
Christopher Schultz el

Respuestas:

36

No hubo un proceso de desarrollo basado en pruebas durante el desarrollo debido a plazos muy ajustados

Esta afirmación es muy preocupante. No porque signifique que se desarrolló sin TDD o porque no está probando todo. Esto es preocupante, porque muestra que piensas que TDD te retrasará y te hará perder una fecha límite.

Mientras lo vea de esta manera, no estará listo para TDD. TDD no es algo en lo que pueda relajarse gradualmente. O sabes cómo hacerlo o no. Si intentas hacerlo a la mitad, lo lograrás y te verás mal.

TDD es algo que primero debes practicar en casa. Aprende a hacerlo, porque te ayuda a codificar ahora . No porque alguien te haya dicho que lo hagas. No porque ayudará cuando hagas cambios más tarde. Cuando se convierte en algo que haces porque tienes prisa, entonces estás listo para hacerlo profesionalmente.

TDD es algo que puedes hacer en cualquier tienda. Ni siquiera tiene que entregar su código de prueba. Puede guardarlo para usted si los demás desdeñan las pruebas. Cuando lo haces bien, las pruebas aceleran tu desarrollo incluso si nadie más las ejecuta.

Por otro lado, si otros aman y ejecutan sus pruebas, debe tener en cuenta que incluso en una tienda TDD no es su trabajo verificar las pruebas. Es crear un código de producción comprobado que funcione. Si resulta ser comprobable, ordenado.

Si cree que la gerencia tiene que creer en TDD o que sus compañeros codificadores deben respaldar sus pruebas, entonces está ignorando lo mejor que TDD hace por usted. Rápidamente le muestra la diferencia entre lo que cree que hace su código y lo que realmente hace.

Si no puede ver cómo eso, por sí solo, puede ayudarlo a cumplir con una fecha límite más rápido, entonces no está listo para TDD en el trabajo. Necesitas practicar en casa.

Dicho esto, es bueno cuando el equipo puede usar sus pruebas para ayudarlos a leer su código de producción y cuando la gerencia comprará nuevas herramientas TDD.

¿Es una buena idea escribir todos los casos de prueba posibles después de transformar el equipo a TDD?

Independientemente de lo que esté haciendo el equipo, no siempre es una buena idea escribir todos los casos de prueba posibles. Escribe los casos de prueba más útiles. La cobertura del 100% del código tiene un costo. No ignore la ley de rendimientos decrecientes solo porque hacer un juicio es difícil.

Ahorre su energía de prueba para la interesante lógica de negocios. Lo que toma decisiones y hace cumplir la política. Prueba el diablo de eso. El código de pegamento estructural obvio, fácil de leer y aburrido que simplemente conecta cosas juntas no necesita pruebas tan mal.

(1) ¿Todavía está bien o es una buena idea detener la mayor parte del desarrollo y comenzar a escribir todos los casos de prueba posibles desde el principio, a pesar de que todo funciona completamente BIEN (¡todavía!)? O

No. Este es el pensamiento de "hagamos una reescritura completa". Esto destruye el conocimiento difícilmente ganado. No le pida a la gerencia tiempo para escribir exámenes. Solo escribe pruebas. Una vez que sepa lo que está haciendo, las pruebas no lo retrasarán.

(2) es mejor esperar a que ocurra algo malo y luego, durante la reparación, escribir nuevas pruebas unitarias, o

(3) incluso olvídate de los códigos anteriores y solo escribe pruebas unitarias para los nuevos códigos y pospone todo al siguiente refactor principal.

Contestaré 2 y 3 de la misma manera. Cuando cambia el código, por cualquier motivo, es realmente bueno si puede pasar una prueba. Si el código es heredado, actualmente no es bienvenido una prueba. Lo que significa que es difícil probarlo antes de cambiarlo. Bueno, como lo estás cambiando de todos modos, puedes cambiarlo en algo comprobable y probarlo.

Esa es la opción nuclear. Es arriesgado. Estás haciendo cambios sin pruebas. Hay algunos trucos creativos para poner a prueba el código heredado antes de cambiarlo. Busca lo que se llama costuras que le permiten cambiar el comportamiento de su código sin cambiar el código. Cambia los archivos de configuración, crea archivos, lo que sea necesario.

Michael Feathers nos dio un libro sobre esto: Trabajando efectivamente con código heredado . Léelo y verás que no tienes que quemar todo lo viejo para hacer algo nuevo.

naranja confitada
fuente
37
"Las pruebas aceleran su desarrollo incluso si nadie más las ejecuta". - Creo que esto es evidentemente falso. Este no es el lugar para comenzar una discusión sobre esto, pero los lectores deben tener en cuenta que el punto de vista presentado aquí no es unánime.
Martin Ba
44
En realidad, a menudo las pruebas aumentan su desarrollo a largo plazo y para que TDD sea realmente eficiente, todos deben creer en ello, de lo contrario, pasaría la mitad de su equipo reparando pruebas rotas por otros.
hspandher
13
"Cree que TDD lo retrasará y lo hará perder una fecha límite". Creo que ese es probablemente el caso. Nadie usa TDD porque esperan que su primer plazo se cumpla más rápido. El beneficio real (al menos en mi opinión) son los dividendos en curso que prueban el juego en el futuro, para detectar regresiones y generar confianza en la experimentación segura. Creo que este beneficio supera el costo inicial de escribir exámenes, la mayoría probablemente estaría de acuerdo, pero si tiene que cumplir con el vencimiento de la fecha límite, realmente no tiene otra opción.
Alexander - Restablece a Mónica el
1
Me parece análogo a comprar una casa. Si tuviera la suma global para pagar una casa, ahorraría mucho en intereses, y sería excelente a largo plazo. Pero si necesita una casa de inmediato ... entonces se ve obligado a adoptar un enfoque a corto plazo que sea subóptimo a largo plazo
Alexander - Restablezca a Monica el
3
TDD = can = aumenta el rendimiento si las pruebas y el código se desarrollan en paralelo, mientras que la funcionalidad está fresca en la mente del desarrollador. Las revisiones de código le dirán si otro ser humano piensa que el código es correcto. Los casos de prueba le dirán si la especificación, tal como está incorporada en un caso de prueba, se está implementando. De lo contrario, sí, TDD puede ser un lastre, especialmente si no hay especificaciones funcionales y el escritor de pruebas también está haciendo ingeniería inversa.
Julie en Austin el
22

¿Todavía está bien o es una buena idea detener la mayor parte del desarrollo y comenzar a escribir todos los casos de prueba posibles desde el principio [...]?

Dado el código heredado 1 , escriba pruebas unitarias en estas situaciones:

  • al arreglar errores
  • cuando refactoriza
  • al agregar nueva funcionalidad al código existente

Por útiles que sean las pruebas unitarias, la creación de un conjunto completo de pruebas unitarias para una base de código 1 existente probablemente no sea una idea realista. Los poderes existentes te han empujado a cumplir en un plazo ajustado. No le dieron tiempo para crear pruebas unitarias adecuadas mientras desarrollaba. ¿Crees que te darán el tiempo adecuado para crear pruebas para el "programa que funciona"?

1 El código heredado es el código sin pruebas unitarias. Esta es la definición TDD de código heredado. Se aplica incluso si el código heredado se entrega recientemente [incluso si la tinta aún no se ha secado].

Nick Alexeev
fuente
Pero entonces nuestras nuevas pruebas unitarias para nuevas características pueden parecer incompletas, o incluso inútiles, sin las pruebas unitarias faltantes. ¿No es así?
Michel Gokan
77
(1) ¿Sin valor? Ciertamente no. Como mínimo, prueban la nueva característica. La próxima vez que alguien quiera modificar esta función, reutilizará gran parte de las pruebas existentes. (2) ¿Incompleto? Tal vez tal vez no. Si también crea pruebas unitarias que prueban la funcionalidad heredada de la que depende la nueva característica, entonces las pruebas pueden ser lo suficientemente completas para fines prácticos. En otras palabras, cree pruebas unitarias adicionales que penetren en la funcionalidad heredada. ¿Penetrar a qué profundidad? Depende de la arquitectura del programa, los recursos disponibles, el apoyo institucional.
Nick Alexeev el
La desventaja de "escribir pruebas cuando tropieza con la necesidad" es que existe un mayor riesgo de terminar con un mosaico de pruebas escritas por diferentes desarrolladores con diferentes ideas. No digo que esta respuesta sea incorrecta, pero requiere una mano firme que mantenga uniforme la calidad y el estilo de las pruebas.
Flater
44
La uniformidad @Flater ofrece una falsa comodidad. Quiero pruebas que faciliten la lectura del código de producción. No pruebas que todos se vean iguales. Perdonaré mezclar marcos de prueba completamente diferentes si hace que sea más fácil entender lo que hace el código de producción.
candied_orange
2
@Flater No afirmé el código de producción feo. Afirmo que el objetivo de las pruebas es hacer que el código de producción sea legible. Con mucho gusto aceptaré una multitud ecléctica de pruebas que hacen que el código de producción sea más fácil de leer. Tenga cuidado al hacer de la uniformidad un objetivo en sí mismo. La legibilidad es el rey.
candied_orange
12

En mi experiencia, las pruebas no necesitan cobertura total para ser útiles. En cambio, comienza a cosechar diferentes tipos de beneficios a medida que aumenta la cobertura:

  • Más del 30% de cobertura (también conocido como un par de pruebas de integración): si sus pruebas fallan, algo está extremadamente roto (o sus pruebas son escamosas). Afortunadamente, las pruebas te alertaron rápidamente. Pero las versiones aún requerirán extensas pruebas manuales.
  • Más del 90% de cobertura (es decir, la mayoría de los componentes tienen pruebas unitarias superficiales): si sus pruebas pasan, es probable que el software esté bien. Las partes no probadas son casos extremos, lo cual está bien para software no crítico. Pero las versiones aún requerirán algunas pruebas manuales.
  • cobertura muy alta de funciones / declaraciones / ramas / requisitos: está viviendo el sueño TDD / BDD , y sus pruebas son un reflejo preciso de la funcionalidad de su software. Puede refactorizar con gran confianza, incluidos los cambios arquitectónicos a gran escala. Si pasan las pruebas, su software está casi listo para su lanzamiento; solo se requieren algunas pruebas de humo manuales.

La verdad es que si no comienzas con BDD nunca llegarás allí, porque el trabajo requerido para probar después de la codificación es excesivo. El problema no es escribir las pruebas, sino más bien tener en cuenta los requisitos reales (en lugar de los detalles de implementación incidentales) y poder diseñar el software de una manera que sea funcional y fácil de probar. Cuando escribe las pruebas primero o junto con el código, esto es prácticamente gratis.

Dado que las nuevas funciones requieren pruebas, pero las pruebas requieren cambios de diseño, pero la refactorización también requiere pruebas, tiene un pequeño problema con el huevo y la gallina. A medida que su software se acerca a una cobertura decente, tendrá que realizar una refactorización cuidadosa en aquellas partes del código donde ocurren nuevas características, solo para que las nuevas características sean verificables. Esto te retrasará mucho, inicialmente. Pero al refactorizar y probar solo aquellas partes donde se necesita un nuevo desarrollo, las pruebas también se enfocan en el área donde más se necesitan. El código estable puede continuar sin pruebas: si tuviera errores, tendría que cambiarlo de todos modos.

Mientras intenta adaptarse a TDD, una mejor métrica que la cobertura total del proyecto sería la cobertura de prueba en las partes que se están cambiando. Esta cobertura debe ser muy alta desde el principio, aunque no es factible probar todas las partes del código que se ven afectadas por una refactorización. Además, obtendrá la mayoría de los beneficios de una alta cobertura de prueba dentro de los componentes probados. Eso no es perfecto, pero sigue siendo bastante bueno.

Tenga en cuenta que si bien las pruebas unitarias parecen ser comunes, comenzar con las piezas más pequeñas no es una estrategia adecuada para obtener un software heredado bajo prueba. Querrá comenzar con pruebas de integración que ejerciten una gran parte del software a la vez.

Por ejemplo, he encontrado útil extraer casos de prueba de integración de archivos de registro del mundo real. Por supuesto, ejecutar tales pruebas puede llevar mucho tiempo, por lo que es posible que desee configurar un servidor automatizado que ejecute las pruebas regularmente (por ejemplo, un servidor Jenkins activado por commits). El costo de configurar y mantener un servidor de este tipo es muy pequeño en comparación con no ejecutar pruebas regularmente, siempre que las fallas de las pruebas se solucionen rápidamente.

amon
fuente
"Más del 90% de cobertura (es decir, la mayoría de los componentes tienen pruebas unitarias superficiales): si sus pruebas pasan, es probable que el software esté bien. Las partes no probadas son casos extremos, lo cual está bien para el software no crítico ". Esto me parece un poco extraño, FWIW, preferiría tener una cobertura del 30% que consista en su mayoría de casos extremos que una cobertura del 90% que consista completamente en el comportamiento esperado de la ruta (que es fácil de hacer para los probadores manuales); Recomiendo pensar "fuera de la caja" al escribir pruebas y basarlas en casos de prueba (inusuales) descubiertos manualmente siempre que sea posible.
jrh
5

No escriba pruebas para el código existente. Que no vale la pena.

Lo que hiciste ya se probó de una manera completamente informal: lo probaste a mano constantemente, las personas hicieron algunas pruebas no automáticas, ahora se está usando. Eso significa que no encontrarás muchos errores .

Lo que queda son los errores en los que no pensaste. Pero esos son exactamente aquellos para los que no pensará escribir pruebas unitarias, por lo que probablemente aún no los encuentre.

Además, una razón para TDD es hacerle pensar cuáles son los requisitos exactos de un bit de código antes de escribirlo. De cualquier manera diferente, ya lo hiciste.

Mientras tanto, todavía es tanto trabajo escribir estas pruebas como lo habría sido escribirlas de antemano. Costará mucho tiempo, por poco beneficio.

Y es extremadamente aburrido escribir muchas pruebas sin codificación en el medio y sin encontrar apenas errores. Si comienzas a hacer esto, las personas nuevas en TDD lo odiarán .

En resumen, los desarrolladores lo odiarán y los gerentes lo verán como costoso, mientras que no se encuentran muchos errores. Nunca llegarás a la parte TDD real.

Úselo en las cosas que desea cambiar, como parte normal del proceso.

RemcoGerlich
fuente
1
No estoy de acuerdo con "No escriba pruebas para el código existente. No vale la pena". Si el código funciona razonablemente correctamente, las pruebas pueden ser la única especificación existente. Y si el código está en mantenimiento, agregar pruebas es la única forma de garantizar que las funciones que funcionan no se rompan por cambios aparentemente no relacionados.
Julie en Austin el
3
@JulieinAustin: Por otro lado, sin una especificación, no sabes exactamente qué se supone que debe hacer el código. Y si aún no sabe lo que se supone que debe hacer el código, bien puede escribir pruebas inútiles, o peor, engañosas que cambian sutilmente las especificaciones, y ahora se requiere un comportamiento accidental y / o incorrecto.
cHao
2

Una prueba es un medio para comunicar comprensión.

Por lo tanto, solo escriba pruebas de lo que entiende que debe ser cierto.

Solo puedes entender lo que debería ser cierto cuando trabajas con él.

Por lo tanto, solo escriba pruebas para el código con el que está trabajando.

Cuando trabajes con el código aprenderás.

Por lo tanto, escribe y reescribe pruebas para capturar lo que has aprendido.

Enjuague y repita.

Ejecute una herramienta de cobertura de código con sus pruebas y solo acepte confirmaciones en la línea principal que no reduzcan la cobertura. Eventualmente alcanzará un alto nivel de cobertura.

Si hace tiempo que no trabaja con el código, debe tomar una decisión comercial. Ahora es tan posible que nadie en su equipo sepa cómo trabajar con él. Probablemente tiene bibliotecas / compiladores / documentación desactualizados, lo cual es una responsabilidad enorme en casi todos los sentidos.

Dos opciones:

  1. Invierta el tiempo para leerlo, aprender de él, escribir pruebas para él y refactorizarlo. Pequeños conjuntos de cambios con lanzamientos frecuentes.
  2. Encuentre una manera de deshacerse de ese software. De todos modos, no podría modificarlo cuando se le solicite.
Kain0_0
fuente
0

(1) ¿Todavía está bien o es una buena idea detener la mayor parte del desarrollo y comenzar a escribir todos los casos de prueba posibles desde el principio, a pesar de que todo funciona completamente BIEN (¡todavía!)?
(2) es mejor esperar a que ocurra algo malo y luego, durante la reparación, escribir nuevas pruebas unitarias

Uno de los principales propósitos de las pruebas es garantizar que un cambio no rompa nada. Este es un proceso de tres pasos:

  1. Confirme que las pruebas tengan éxito
  2. Hacerte cambios
  3. Confirme que las pruebas aún tienen éxito

Esto significa que necesita tener pruebas de funcionamiento antes de cambiar algo realmente. Si elige la segunda ruta, eso significa que tendrá que obligar a sus desarrolladores a escribir pruebas incluso antes de que toquen el código. Y sospecho fuertemente que cuando ya se enfrentan a un cambio en el mundo real, los desarrolladores no van a dar a las pruebas unitarias la atención que merecen.

Por lo tanto, sugiero dividir las tareas de redacción de pruebas y cambios para evitar que los desarrolladores sacrifiquen la calidad de uno por el otro.

a pesar de que todo funciona completamente bien (¡todavía!)?

Solo para señalar esto específicamente, es un error común pensar que solo necesita pruebas cuando el código no funciona. También necesita pruebas cuando el código funciona, por ejemplo, para demostrarle a alguien que [el nuevo error] no se debe a su parte porque las pruebas aún se están aprobando.
Confirmar que todo sigue funcionando como lo hizo antes es un beneficio importante de las pruebas que está omitiendo cuando implica que no necesita pruebas cuando el código está funcionando.

(3) incluso olvídate de los códigos anteriores y solo escribe pruebas unitarias para los nuevos códigos y pospone todo al siguiente refactor principal

Idealmente, todo el código fuente existente ahora debería obtener pruebas unitarias. Sin embargo, existe un argumento razonable de que el tiempo y el esfuerzo (y el costo) necesarios para hacerlo simplemente no son relevantes para ciertos proyectos.
Por ejemplo, las aplicaciones que ya no se están desarrollando y ya no se espera que se cambien (por ejemplo, el cliente ya no lo usa, o el cliente ya no es un cliente), puede argumentar que ya no es relevante probar este código .

Sin embargo, no es tan claro donde dibujas la línea. Esto es algo que una empresa debe considerar en un análisis de costo beneficio. Escribir pruebas cuesta tiempo y esfuerzo, pero ¿esperan algún desarrollo futuro en esa aplicación? ¿Las ganancias de tener pruebas unitarias superan el costo de escribirlas?

Esta no es una decisión que usted (como desarrollador) pueda tomar. En el mejor de los casos, puede ofrecer una estimación del tiempo necesario para implementar las pruebas en un proyecto determinado, y depende de la gerencia decidir si existe una expectativa suficiente de la necesidad de mantener / desarrollar el proyecto.

y posponer todo al siguiente refactor principal

Si el siguiente refactor principal es un hecho, entonces sí necesita escribir las pruebas.

Pero no lo posponga hasta que enfrente cambios importantes. Mi punto inicial (no combinar la redacción de pruebas y actualizar el código) sigue en pie, pero quiero agregar un segundo punto aquí: sus desarrolladores actualmente conocen mejor el proyecto que en seis meses si pasan ese tiempo trabajando en otros proyectos Aproveche los períodos de tiempo en los que los desarrolladores ya se han calentado y no necesitan descubrir cómo volverán a funcionar las cosas en el futuro.

Flater
fuente
0

Mis dos centavos:

Espere una importante actualización técnica del sistema y escriba las pruebas ... oficialmente con el apoyo de la empresa.

Alternativamente, supongamos que es una tienda SCRUM, su carga de trabajo está representada por la capacidad y puede asignar un% de eso a las pruebas unitarias, pero ...

Decir que vas a volver y escribir las pruebas es ingenuo, lo que realmente vas a hacer es escribir pruebas, refactorizar y escribir más pruebas después de que el refactor haya hecho que el código sea más verificable, por lo que es mejor comenzar con pruebas como ya sabes, y ...

Es mejor que el autor original escriba pruebas y refactorice el código que escribió anteriormente, no es ideal, pero por experiencia desea que el refactor mejore el código y no lo empeore.

RandomUs1r
fuente