Mejores prácticas para actualizar código heredado con pruebas automatizadas

22

Estoy a punto de asumir la tarea de volver a implementar una interfaz ya definida (un conjunto de archivos de encabezado C ++) en una base de código relativamente grande y antigua. Antes de hacer esto, me gustaría tener una cobertura de prueba tan completa como sea posible, para poder detectar errores de reimplementación tan pronto y fácilmente como sea posible. El problema es que la base de código ya existente no fue diseñada para ser fácilmente comprobable, con (muy) grandes clases y funciones, un alto grado de acoplamiento, funciones con (muchos) efectos secundarios, etc.

Sería bueno saber de cualquier experiencia previa con tareas similares, y algunos consejos buenos y concretos sobre cómo hizo para adaptar las pruebas automatizadas (unidad, integraciones, regresión, etc.) a su código heredado.

tjansson
fuente
1
Paso 1: Buscar desbordamiento de pila. La pregunta ha sido formulada. Muchas, muchas veces.
S.Lott

Respuestas:

20

En primer lugar, obtenga y lea Trabajar eficazmente con código heredado de Michael Feathers: es una ayuda indispensable para tales tareas.

Luego, algunas notas:

  • ¿tiene una especificación / contrato preciso para la interfaz, o prácticamente solo tiene la implementación existente como "especificación"? En el primer caso, es más fácil hacer una reescritura completa desde cero, en el último es difícil o imposible.
  • Si desea volver a implementar la interfaz, la forma más útil de gastar sus recursos de prueba es escribir pruebas solo en la interfaz. Por supuesto, esto no califica como pruebas unitarias en sentido estricto, sino pruebas funcionales / de aceptación, pero no soy un purista :-) Sin embargo, estas pruebas son reutilizables y le permiten comparar directamente los resultados de las dos implementaciones una al lado de la otra. .
  • en general, preferiría refactorizar el código existente en lugar de reescribirlo desde cero, a menos que sea completamente imposible de mantener. (Pero en este caso, ¿cómo vas a escribir pruebas unitarias en su contra de todos modos?) Mira esta publicación de Joel para una discusión más detallada sobre el tema. El hecho de haber creado un conjunto de pruebas de aceptación contra la interfaz le brinda una red de seguridad delgada pero útil, contra la cual puede comenzar a refactorizar con precaución el código existente para hacer que la unidad sea comprobable (utilizando las ideas del libro de Feathers).
Péter Török
fuente
Haría +3 esto si pudiera. WELC es una lectura esencial y definitivamente va a refactorizar ...
Johnsyweb
Un comentario menor sobre el segundo punto es que para los sistemas heredados, las pruebas deben realizarse de acuerdo con la mentalidad de prueba de caracterización . Es decir, capturar fielmente el comportamiento actual del software y abstenerse de cambiar el comportamiento, incluso si algunos de los resultados de las pruebas parecen extraños o no están de acuerdo con la mentalidad de las pruebas unitarias. (Por cierto, esta idea también proviene del autor de WELC.)
rwong
@rwong, de hecho. Sin una especificación detallada, o un propietario de producto bien informado, es imposible que el desarrollador decida si un comportamiento específico del programa a) es intencional y requerido, b) no fue intencional pero ahora los usuarios dependen de él, c) un error que en realidad daña a los usuarios, d) un error totalmente inadvertido hasta ahora. En los primeros dos casos, "arreglar" realmente dañaría a los usuarios, y en el último caso, la corrección, aunque teóricamente correcta, no proporcionaría ningún beneficio visible.
Péter Török
4

El mejor método es saber es el Método Mikado. http://mikadomethod.wordpress.com/2010/08/04/the-mikado-method-book/ Esto es solo la generalización de una técnica simple, pero es la única manera que sé para comenzar a mejorar la calidad del código en una base de código grande sin correr riesgos innecesarios.

WEWLC también es un libro muy bueno al respecto, pero estar escrito en C ++ no siempre es útil con código Java o Ruby.

Uberto
fuente
2

Las pruebas de ajuste retro en una base de código antigua pueden ser bastante difíciles si su diseño es monolítico.

Si es posible (tiene el tiempo / dinero), una forma de avanzar sería refactorizando el código en unidades más comprobables.

ozz
fuente
1

Me gustaría agregar un enlace . Hay pocos ejemplos de implementaciones no tan fácilmente comprobables refactorizadas en un código más compatible con xUnit. En cuanto al enfoque general, intente con los enlaces ya mencionados (publicación de Joel, código Working With Legacy)

yoosiba
fuente