Escribir pruebas para el código existente

68

Suponga que uno tiene un programa relativamente grande (digamos 900k SLOC en C #), todos comentados / documentados a fondo, bien organizados y funcionando bien. Todo el código base fue escrito por un único desarrollador senior que ya no está en la compañía. Todo el código se puede probar tal cual y se utiliza IoC en todo momento, excepto por alguna extraña razón por la que no escribieron ninguna prueba unitaria. Ahora, su empresa quiere ramificar el código y quiere que se agreguen pruebas unitarias para detectar cuándo los cambios rompen la funcionalidad principal.

  • ¿Agregar pruebas es una buena idea?
  • Si es así, ¿cómo podría uno comenzar con algo como esto?

EDITAR

Bien, entonces no esperaba respuestas con buenos argumentos para conclusiones opuestas. El problema puede estar fuera de mis manos de todos modos. También he leído las "preguntas duplicadas" y el consenso general es que "escribir exámenes es bueno" ... sí, pero no demasiado útil en este caso en particular.

No creo que esté solo aquí contemplando escribir pruebas para un sistema heredado. Voy a mantener métricas sobre cuánto tiempo se gasta y cuántas veces las nuevas pruebas detectan problemas (y cuántas veces no lo hacen). Volveré y actualizaré esto dentro de un año más o menos con mis resultados.

CONCLUSIÓN

Entonces resulta que es básicamente imposible agregar una prueba unitaria al código existente con cualquier apariencia de ortodoxia. Una vez que el código está funcionando, obviamente no puede iluminar en rojo / en verde sus pruebas, por lo general no está claro qué comportamientos son importantes para probar, no está claro por dónde comenzar y ciertamente no está claro cuando haya terminado. Realmente, incluso hacer esta pregunta pierde el punto principal de escribir pruebas en primer lugar. En la mayoría de los casos, encontré que en realidad era más fácil reescribir el código usando TDD que descifrar las funciones previstas y agregar retroactivamente pruebas unitarias. Al solucionar un problema o agregar una nueva característica, es una historia diferente, y creo que este es el momento de agregar pruebas unitarias (como algunos señalan a continuación). Con el tiempo, la mayoría del código se reescribe, a menudo antes de lo que cabría esperar, adoptando este enfoque.

Pablo
fuente
10
Agregar pruebas no romperá el código existente.
Dan Pichelman
30
@DanPichelman Nunca ha experimentado un error de schroedinbug : "Un error de diseño o implementación en un programa que no se manifiesta hasta que alguien que lee la fuente o utiliza el programa de manera inusual se da cuenta de que nunca debería haber funcionado, en ese momento el programa rápidamente deja de funcionar para todos hasta que se arregla ".
8
@MichaelT Ahora que lo mencionas, creo que he visto uno o dos de esos. Mi comentario debería haber leído "Agregar pruebas generalmente no romperá el código existente". Gracias
Dan Pichelman
3
Solo escriba pruebas alrededor de cosas que pretende refactorizar o cambiar.
Steven Evers
3
Consulte el libro "Trabajar eficazmente con código heredado": amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/… , Michael Feathers sugiere escribir las pruebas antes de modificar cualquier código heredado.
Skarab

Respuestas:

68

Si bien las pruebas son una buena idea, la intención era que el codificador original las construyera mientras creaba la aplicación para capturar su conocimiento de cómo se supone que funciona el código y qué puede romperse, lo que luego le habría sido transferido.

Al adoptar este enfoque, existe una alta probabilidad de que escriba las pruebas que tienen menos probabilidades de romperse, y se pierda la mayoría de los casos límite que se habrían descubierto al crear la aplicación.

El problema es que la mayor parte del valor provendrá de esas 'trampas' y situaciones menos obvias. Sin esas pruebas, el conjunto de pruebas pierde prácticamente toda su efectividad. Además, la compañía tendrá una falsa sensación de seguridad en torno a su aplicación, ya que no será significativamente más prueba de regresión.

Por lo general, la forma de manejar este tipo de base de código es escribir pruebas para el nuevo código y para la refactorización del código antiguo hasta que la base de código heredada se refactorice por completo.

También ver .

FMJaguar
fuente
11
Pero, incluso sin TDD, las pruebas unitarias son útiles al refactorizar.
pdr
1
Si el código continúa funcionando bien, no es un problema, pero lo mejor sería probar la interfaz con el código heredado cada vez que escriba algo que dependa de su comportamiento.
deworde
1
Es por eso que mide la cobertura de la prueba . Si la prueba para una sección en particular no cubre todos los ifs y elses y todos los casos límite, entonces no puede refactorizar esa sección de manera segura. La cobertura le indicará si se tocan todas las líneas, por lo que su objetivo es aumentar la cobertura tanto como sea posible antes de refactorizar.
Rudolf Olah
3
Una deficiencia importante de TDD es que, si bien la suite puede ejecutarse, para el desarrollador que no está familiarizado con la base del código, esta es una falsa sensación de seguridad. BDD es mucho mejor en este aspecto ya que la salida es la intención del código en inglés simple.
Robbie Dee
3
Solo quiero mencionar que la cobertura del 100% del código no significa que su código funcione correctamente el 100% del tiempo. Puede probar todas las líneas de código para el método, pero el hecho de que funcione con value1 no significa que esté garantizado que funcione con value2.
ryanzec
35

Sí, agregar pruebas es definitivamente una buena idea.

Dices que está bien documentado y eso te coloca en una buena posición. Intente crear pruebas utilizando esa documentación como guía, enfocándose en partes del sistema que son críticas o están sujetas a cambios frecuentes.

Inicialmente, el gran tamaño de la base de código probablemente parecerá abrumador en comparación con la pequeña cantidad de pruebas, pero no existe un enfoque de big bang, y comenzar en algún lugar es más importante que agonizar sobre cuál será el mejor enfoque.

Recomiendo el libro de Michael Feathers, Working Effectively with Legacy Code , para algunos consejos buenos y detallados.

Danny Woods
fuente
10
+1. Si escribe las pruebas basadas en la documentación, descubrirá cualquier discrepancia entre el código de trabajo y la documentación, y eso es invaluable.
Carl Manaster
1
Otra razón para agregar pruebas: cuando se encuentra un error, puede agregar fácilmente un caso de prueba para futuras pruebas de regresión.
Esta estrategia es básicamente la que se describe en el curso en línea (gratuito) edX CS169.2x en el capítulo sobre código heredado. Como lo dicen los maestros: "Establecer la verdad básica
MGF
21

No todas las pruebas unitarias tienen el mismo beneficio. El beneficio de una prueba unitaria se produce cuando falla. Cuanto menos probable es que falle, menos beneficioso es. Es más probable que el código nuevo o modificado recientemente contenga errores que el código que rara vez se cambia y que se prueba bien en producción. Por lo tanto, es más probable que las pruebas unitarias en código nuevo o modificado recientemente sean más beneficiosas.

No todas las pruebas unitarias tienen el mismo costo. Es mucho más fácil probar el código trivial que diseñó usted mismo hoy que el código complejo que alguien más diseñó hace mucho tiempo. Además, las pruebas durante el desarrollo generalmente ahorran tiempo de desarrollo. En el código heredado, ese ahorro de costos ya no está disponible.

En un mundo ideal, tendría todo el tiempo que necesita para probar el código heredado de la unidad, pero en el mundo real, en algún momento es lógico que los costos de agregar pruebas unitarias al código heredado superen los beneficios. El truco es identificar ese punto. Su control de versiones puede ayudar mostrándole el código que se modificó más recientemente y el que se modificó con más frecuencia, y puede comenzar poniéndolos bajo prueba unitaria. Además, cuando realice cambios en el futuro, coloque esos cambios y el código estrechamente relacionado bajo prueba unitaria.

Siguiendo ese método, eventualmente tendrá una cobertura bastante buena en las áreas más beneficiosas. Si, en cambio, pasa meses realizando pruebas unitarias antes de reanudar las actividades generadoras de ingresos, esa podría ser una decisión de mantenimiento de software deseable, pero es una pésima decisión comercial.

Karl Bielefeldt
fuente
3
Si el código rara vez falla, se podría dedicar mucho tiempo y esfuerzo a encontrar problemas arcanos que nunca podrían ocurrir en la vida real. Por otro lado, si el código tiene errores y es propenso a errores, probablemente podría comenzar a probar en cualquier lugar y encontrar problemas de inmediato.
Robbie Dee
8

¿Agregar pruebas es una buena idea?

Absolutamente, aunque me resulta un poco difícil creer que el código esté limpio y funcione bien y que use técnicas modernas y simplemente no tenga pruebas unitarias. ¿Estás seguro de que no están sentados en una solución separada?

De todos modos, si vas a extender / mantener el código, las verdaderas pruebas unitarias son invaluables para ese proceso.

Si es así, ¿cómo podría uno comenzar con algo como esto?

Un paso a la vez. Si no está familiarizado con las pruebas unitarias, aprenda un poco. Una vez que se sienta cómodo con los conceptos, elija una pequeña sección del código y escriba pruebas para ello. Luego el siguiente y el siguiente. La cobertura de código puede ayudarlo a encontrar lugares que se ha perdido.

Probablemente sea mejor elegir cosas peligrosas / arriesgadas / vitales para probar primero, pero puede ser más efectivo probar algo directo para comenzar primero, especialmente si usted / el equipo no está acostumbrado a la base de código y / o unidad pruebas.

Telastyn
fuente
77
"¿Estás seguro de que no están sentados en una solución separada?" Es una gran pregunta. Espero que el OP no lo pase por alto.
Dan Pichelman
Desafortunadamente, la aplicación se inició hace muchos años justo cuando TDD estaba ganando terreno, por lo que la intención era hacer pruebas en algún momento, pero por alguna razón, una vez que comenzó el proyecto, nunca llegaron a ella.
Paul
2
Probablemente nunca lo consiguieron porque les habría llevado un tiempo extra que no valía la pena. Un buen desarrollador que trabaje solo definitivamente puede crear una aplicación limpia, clara, bien organizada y funcional de un tamaño relativamente grande sin pruebas automáticas, y en general puede hacerlo más rápido y sin errores como con las pruebas. Dado que todo está en su propia cabeza, hay una probabilidad significativamente menor de errores o problemas de organización en comparación con el caso de múltiples desarrolladores que lo crean.
Ben Lee
3

Sí, tener pruebas es una buena idea. Ayudarán a documentar el código base existente que funciona según lo previsto y detectarán cualquier comportamiento inesperado. Incluso si las pruebas fallan inicialmente, déjelos y luego refactorice el código más tarde para que pasen y se comporten según lo previsto.

Comience a escribir pruebas para clases más pequeñas (las que no tienen dependencias y son relativamente simples) y pase a clases más grandes (las que tienen dependencias y son más complejas). Tomará mucho tiempo, pero sea paciente y persistente para que eventualmente pueda cubrir la base de código tanto como sea posible.

Bernardo
fuente
¿Realmente agregaría pruebas fallidas a un programa que funciona bien (dice el OP)?
MarkJ
Sí, porque muestra que algo no funciona según lo previsto y requiere una revisión adicional. Esto provocará una discusión y, con suerte, corregirá cualquier malentendido o defecto previamente desconocido.
Bernard
@Bernard - o, las pruebas pueden exponer su malentendido de lo que se supone que debe hacer el código. Las pruebas escritas después del hecho corren el riesgo de no encapsular correctamente las intenciones originales.
Dan Pichelman
@DanPichelman: De acuerdo, pero esto no debería desanimar a nadie a escribir ninguna prueba.
Bernard
Por lo menos, indicaría que el código no se ha escrito a la defensiva.
Robbie Dee
3

OK, voy a dar la opinión contraria ...

Agregar pruebas a un sistema de trabajo existente va a alterar ese sistema, a menos que sea así, todo el sistema está escrito con una burla en mente desde el principio. Lo dudo, aunque es muy posible que tenga una buena separación de todos los componentes con límites fácilmente definibles en los que puede deslizar sus interfaces simuladas. Pero si no es así, entonces tendrá que hacer cambios bastante significativos (en términos relativos) que pueden romper las cosas. En el mejor de los casos, pasará una gran cantidad de tiempo escribiendo estas pruebas, tiempo que podría emplearse mejor escribiendo una carga de documentos de diseño detallados, documentos de análisis de impacto o documentos de configuración de soluciones. Después de todo, ese es el trabajo que su jefe quiere hacer más que las pruebas unitarias. ¿No es así?

De todos modos, no agregaría ninguna prueba unitaria.

Me concentraría en herramientas de prueba externas y automatizadas que le brindarán una cobertura razonable sin cambiar nada. Entonces, cuando vengas a hacer modificaciones ... ahí es cuando puedes comenzar a agregar pruebas unitarias dentro de la base de código.

gbjbaanb
fuente
2

A menudo me he encontrado con esta situación, heredé una gran base de código sin una cobertura de prueba adecuada o nula, y ahora soy responsable de agregar funcionalidad, corregir errores, etc.

Mi consejo es asegurarse y probar lo que agrega, y si corrige errores o altera los casos de uso en el código actual, las pruebas de autor luego. Si tiene que tocar algo, escriba pruebas en ese punto.

Cuando esto se descompone es cuando el código existente no está bien estructurado para las pruebas unitarias, por lo que pasa mucho tiempo refactorizando para poder agregar pruebas para cambios menores.

Casey
fuente