2009-03-18 11 views
5

Actualmente estoy leyendo dos libros excelentes "Trabajando eficazmente con código heredado" y "Código limpio".Refactorización y desarrollo impulsado por prueba

Me están haciendo pensar en la forma en que escribo y trabajo con código de formas completamente nuevas, pero uno de sus temas es el desarrollo basado en pruebas y la idea de sofocar todo con pruebas y realizar pruebas antes de realizar un cambio o implementar una nueva pieza de funcionalidad.

Esto ha dado lugar a dos preguntas:

Pregunta 1: Si estoy trabajando con el código heredado. De acuerdo con los libros, debería poner pruebas para asegurar que no estoy rompiendo nada. Considera que tengo un método de 500 líneas de largo. Asumiría que tendré un conjunto de métodos de prueba equivalentes para probar ese método. Cuando divido esta función, ¿creo nuevas pruebas para cada nuevo método/clase que resulte?

De acuerdo con "Código de limpieza", cualquier prueba que lleve más de 1/10 de segundo es una prueba que lleva demasiado tiempo. Tratando de probar un método antiguo de 500 líneas que va a las bases de datos y sabe Dios qué más podría tomar más de 1/10 de segundo. Si bien entiendo que necesitas romper las dependencias, lo que estoy teniendo problemas es la creación de la prueba inicial.

Pregunta 2: ¿Qué sucede cuando el código se vuelve a factorizar tanto que estructuralmente ya no se parece al código original (nuevos parámetros agregados/eliminados a métodos, etc.). ¿De esto se desprendería que las pruebas necesitarán volver a factorizar también? En ese caso, ¿podría alterar la funcionalidad del sistema mientras permite que las pruebas sigan pasando? ¿Las pruebas de refactorización son una medida apropiada para hacer en esta circunstancia?

Si bien está bien seguir con las suposiciones, me preguntaba si hay algún pensamiento/sugerencia sobre estos asuntos de una experiencia colectiva.

+0

refactorización es la conservación del comportamiento - impuesto por sus pruebas. Entonces, si cambias el comportamiento a través de tus modificaciones, ya no estás refactorizando. Para el código heredado, agrega pruebas que actúan como un vicio para mantener el SUT en su lugar mientras mejora el diseño hacia la capacidad de prueba. Estas pruebas pueden ser lentas ... la guía de 0.1s es para pruebas de microtests/unit. La razón de esa directriz es que podría tener miles de pruebas tan pequeñas ... si toman 0.1 cada una, podría estar esperando mucho tiempo cada vez que las ejecute. – Gishu

Respuesta

4
  1. Ese es el problema cuando se trabaja con código heredado. Legacy significa un sistema sin pruebas y que está estrechamente vinculado. Al agregar pruebas para ese código, efectivamente está agregando pruebas de integración.Cuando refactorice y agregue los métodos de prueba más específicos que eviten las llamadas de red, etc. esas serían sus pruebas unitarias. Desea mantener ambos, simplemente sepárese, de esa forma la mayoría de las pruebas de su unidad se ejecutarán rápidamente.
  2. Lo haces en pasos realmente pequeños. Realmente cambias continuamente entre las pruebas y el código, y estás en lo cierto; si cambias una firma (paso pequeño), es necesario actualizar las pruebas relacionadas.

También verifique mi "actualización 2" en How can I improve my junit tests. No se trata de código heredado y tratar con el acoplamiento que ya tiene, sino en cómo vas acerca de cómo escribir la lógica + pruebas en las que están implicados los sistemas externos de bases de datos es decir, correos electrónicos, etc.

2

El tiempo de ejecución de prueba de la unidad 0.1s es bastante tonto. No hay ninguna razón para que las pruebas unitarias no utilicen un socket de red, lean un archivo grande u otras operaciones pesadas si es necesario. Sí, es bueno si las pruebas se ejecutan rápidamente para que pueda continuar con el trabajo principal de escribir la aplicación, pero es mucho mejor terminar con el mejor resultado al final y si eso significa ejecutar una prueba unitaria que toma 10s, entonces eso es lo que Haría

Si va a refactorizar la clave es pasar todo el tiempo que necesite para comprender el código que está refabricando. Una buena forma de hacerlo sería escribir algunas pruebas unitarias para ello. A medida que comprenda qué hacen ciertos bloques de código, podría refactorizarlo y, a medida que avanza, es una buena práctica escribir pruebas para cada uno de sus nuevos métodos.

+0

Lo siento, pero hay muchas razones en el mundo en que una prueba de unidad no debe usar un socket de base de datos, leer un archivo grande o cualquier otra operación importante. Sin embargo, la razón principal es que si una prueba realmente lo hizo, sería una prueba de integración y no una prueba unitaria.Se supone que debes burlar tus dependencias para que no ocurra ninguna E/S real y se pueda probar la lógica de tu código sin confiar, por ejemplo, en que tu base de datos funcione correctamente. Es por eso que un objetivo 0.1s es totalmente razonable, porque no ocurre una E/S real. –

1
    • Sí, crear nuevas pruebas para los nuevos métodos.

    • Vería el objetivo de 1/10 de segundo como meta. Una prueba más lenta es mucho mejor que ninguna prueba.

  1. Intente no cambiar el código y la prueba al mismo tiempo. Siempre tome pequeños pasos.

+0

Una prueba más lenta puede estar bien, a menos que sea tan lenta que las personas no la ejecuten con la suficiente frecuencia. – JeffH

0

Aquí está mi opinión sobre ella:

  1. No y sí. Primero lo primero es tener una prueba unitaria que verifique el resultado de ese método de 500 líneas. Y luego eso es solo cuando comienzas a pensar en dividirlo. Lo ideal sería que el proceso será algo así:

    • escribir una prueba para el legado original de 500 líneas gigante
    • figura a cabo, marcando primero con los comentarios, lo que los bloques de código que podrían extraer de ese método
    • Comentario una prueba para cada bloque de código. Todos fallarán
    • Extraiga los bloques uno por uno. Concéntrese en hacer que todos los métodos se vuelvan verdes, uno por uno.
    • Enjuague y repita hasta que haya terminado todo el asunto

    Después de este largo proceso se dará cuenta de que podría tener sentido que algunos métodos pueden mover a otra parte, o son repetitivas y varios se pueden reducir a una sola función; así es como sabes que has tenido éxito. Edite las pruebas en consecuencia.

  2. Continúe y refactorice, pero tan pronto como necesite cambiar las firmas, realice primero los cambios en su prueba antes de realizar el cambio en su código real. De esta forma, se asegura de que siga realizando las afirmaciones correctas dado el cambio en la firma del método.

1

Cuando tienes un método heredado largo que hace X (y tal vez Y y Z debido a su tamaño), el verdadero truco no está rompiendo la aplicación por 'fijación' la misma. Las pruebas de la aplicación heredada tienen condiciones previas y posteriores, por lo que debe conocerlas antes de romperlas. Las pruebas ayudan a facilitar eso. Tan pronto como divida ese método en dos o más métodos nuevos, obviamente necesita conocer los estados previos y posteriores a cada uno de ellos y, por lo tanto, realiza pruebas para que "se mantenga honesto" y le permita dormir mejor por la noche.

No suelo preocuparme demasiado por la afirmación de una décima de segundo. Más bien, el objetivo cuando estoy escribiendo pruebas unitarias es cubrir todas mis bases. Obviamente, si una prueba lleva mucho tiempo, puede ser porque lo que se está probando es simplemente demasiado código haciendo demasiado.

La conclusión es que definitivamente no desea tomar lo que presumiblemente es un sistema en funcionamiento y 'arreglarlo' hasta el punto de que funciona a veces y falla bajo ciertas condiciones. Ahí es donde las pruebas pueden ayudar. Cada uno de ellos espera que el mundo esté en un estado al comienzo de la prueba y un nuevo estado al final. Solo tú puedes saber si esos dos estados son correctos. Todas las pruebas pueden 'pasar' y la aplicación todavía puede estar equivocada.

Cada vez que se cambie el código, las pruebas posiblemente cambien y es probable que se tengan que agregar nuevas para hacer cambios en la dirección del código de producción. Esas pruebas funcionan con el código actual; no importa si los parámetros deben cambiar, todavía hay condiciones previas/posteriores que deben cumplirse. No es suficiente, obviamente, simplemente dividir el código en pedazos más pequeños. El "analista" en ti tiene que ser capaz de entender el sistema que estás construyendo, ese es el primer trabajo.

Trabajar con código heredado puede ser una verdadera tarea dependiendo del "desastre" con el que empiezas. Realmente creo que saber qué tienes y qué se supone que debe hacer (y si realmente lo hace en el paso 0 antes de comenzar a refactorizarlo) es clave para una refactorización exitosa del código. Un objetivo, creo, es que debería poder deshacerme de las cosas viejas, colocar mi nuevo código en su lugar y hacer que funcione como se anuncia (o mejor). Dependiendo del idioma en el que fue escrito, las suposiciones hechas por el autor (es) original (es) y la capacidad de encapsular la funcionalidad en trozos contables, puede ser un verdadero truco.

¡La mejor de las suertes!

0

Pregunta 1: "Cuando he dividido esta función, ¿creo nuevas pruebas para cada nuevo método/clase resultante?"

Como siempre la verdadera respuesta es depende. Si es apropiado, puede ser más sencillo refaccionar algunos métodos monolíticos gigantescos en métodos más pequeños que manejan diferentes componentes para establecer sus nuevos métodos en privado/protegido y dejar intacta su API existente para continuar usando sus pruebas de unidades existentes. Si necesita probar sus métodos recién divididos, a veces es conveniente marcarlos como paquete privado para que las clases de prueba unitaria puedan acceder a ellos, pero otras clases no pueden.

Pregunta 2: "¿Qué sucede cuando el código se vuelve a factorizar tanto que estructuralmente ya no se parece al código original?"

Mi primer consejo aquí es que necesita obtener un buen IDE y tener un buen conocimiento de las expresiones regulares; intente hacer la mayor parte de su refactorización utilizando herramientas automatizadas como sea posible. Este puede ayudar a ahorrar tiempo si tiene la precaución de no introducir nuevos problemas. Como dijiste, tienes que cambiar las pruebas de tu unidad, pero si usaste buenos directores de POO con (¿lo hiciste bien?), Entonces no debería ser tan doloroso.

En general, es importante preguntarse con respecto al refactorio, ¿los beneficios superan los costos? ¿Estoy jugando con arquitecturas y diseños? ¿Estoy haciendo un refactor para entender el código y realmente lo necesito? Consultaría a un compañero de trabajo que esté familiarizado con la base de código de su opinión sobre los costos/beneficios de su tarea actual.

Recuerde también que el ideal teórico que lee en los libros debe equilibrarse con las necesidades del negocio del mundo real y los horarios.

Cuestiones relacionadas