2010-10-19 14 views
5

Después de meses de frustración y de tiempo dedicado a insertar agujas en muñecos vudú de desarrolladores anteriores, decidí que era mejor intentar refactorizar el código heredado.UnitEvaluación de una clase que devuelve un conjunto de datos complejo

Ya pedí Micheal Feather's book, estoy en Fowler's refactoring y realicé algunos proyectos de muestra con DUnit.

Así que incluso si no domino el tema siento que es hora de actuar y poner en práctica algunas ideas.

Casi el 100% del código en el que trabajo tiene la lógica de negocios atrapada en la interfaz de usuario, además, todo es programación de procedimientos (con algunas pocas excepciones). La aplicación comenzó como rápida & sucia y continuó como tal.

Ahora escribir pruebas para toda la aplicación es una tarea sin sentido en mi caso, pero me gustaría tratar de probar algo que necesito para refactorizar.

Una de las tareas complejas que una gran "clase de lógica comercial de TForm" hace es leer datos de BD, realizar algunos cálculos y rellenar un componente del planificador. Me gustaría eliminar la parte de datos de lectura DB y la parte de cálculo y asignar a una nueva clase esta tarea. Por supuesto, esta es una forma de mejorar el diseño actual, no es la mejor manera de comenzar desde cero, pero me gustaría hacerlo porque los datos devueltos por esta nueva clase también son útiles de otras maneras, por ejemplo, ahora Se me ha pedido que envíe notificaciones por correo electrónico de los datos del planificador.

Para evitar una operación masiva de copiar y pegar, necesito la nueva clase.

Ahora que el planificador se rellena desde un enorme conjunto de datos (enorme en tamaño y en número de campos), probablemente un primer paso de refactorización podría ser obtener el conjunto de datos de la nueva clase. Pero en el futuro, será mejor usar una nueva clase (como TSchedulerData u otro nombre menos vinculado al planificador) para administrar los datos, y en lugar de tener un conjunto de datos como resultado, puedo tener un objeto TSchedulerData.

Dado que el refactor se produce en pequeños pasos y se necesitan pruebas para refactorizar mejor, estoy un poco confundido sobre cómo proceder.

Los siguientes puntos no son claras para mí:

1) la forma de probar un conjunto de datos compleja? ¿Debo ejecutar la aplicación en funcionamiento, guardar un conjunto de resultados en xml y escribir una prueba donde use un TClientDataSet que contenga esos datos xml?

2) ¿Cuánto me tiene que importar TSchedulerData? Quiero decir, no estoy 100% seguro de que usaré TSchedulerData, puede ser que me quede con el Dataset, de todos modos, pensar en crear pruebas complejas que se descartarán en 2 semanas no es atractivo para un DUnitNewbee. De todos modos, probablemente así es como funciona. No me puedo imaginar la cantidad de errores que enfrentaría sin una prueba.

Nota final: Sé que alguien piensa que reescribir desde cero es una mejor opción, pero esta no es una opción. "La aplicación es enorme y se vende hoy y hoy se requieren nuevas funciones para no cerrar". Esto es lo que me han dicho, de todos modos la refactorización puede salvar mi vida y extender la vida de la aplicación.

Respuesta

2

Su objetivo final es separar la interfaz de usuario, el almacenamiento de datos y la lógica empresarial en distintas capas.

Es muy difícil probar una interfaz de usuario con marcos de prueba automáticos. Deseará eventualmente separar la mayor parte de la lógica comercial de la interfaz de usuario como sea posible. Esto se puede lograr utilizando uno de los varios modelos/View/* patrones. Prefiero la vista pasiva de MVP, que intenta hacer que la interfaz de usuario no sea más que una interfaz. Si está utilizando un controlador de supervisión MVP Dataset puede ser una mejor opción.

El almacenamiento de datos debe tener su propio conjunto de pruebas, pero estas son diferentes de las pruebas unitarias (aunque puede usar el mismo marco de pruebas de unidades) y generalmente hay menos. Puede salirse con la suya porque la mayor parte del trabajo pesado lo realizan componentes de datos de terceros y un dbms (en su caso T * Dataset). Estas son pruebas de integración. Básicamente, asegúrate de que tu código funcione bien con el código del proveedor. También es necesario si tiene algún procedimiento almacenado definido en la base de datos. Son mucho más lentas que las pruebas unitarias y no necesitan ejecutarse con tanta frecuencia.

La lógica comercial es lo que más desea probar. Cada cálculo, bucle o sucursal debe tener al menos una prueba (se prefiere más). En el código heredado, esta lógica a menudo toca directamente la interfaz de usuario y la base de datos y hace varias cosas en una sola función. Aquí Método de extracto es tu amigo. Buenos lugares para extraer métodos son:

for I:=0 to List.Count - 1 do 
begin 
    //HERE 
end; 

if /*HERE if its a complex condition*/ then 
begin 
    //HERE 
end 
else 
begin 
    //HERE 
end 

Answer := Var1/Var2 + Var1 * Var3; //HERE 

Cuando te encuentras con uno de estos puntos de extracción

  1. decidir lo que quiere la firma del método para que parezca que el nuevo método: Nombre de método, los parámetros, el regreso valor.
  2. Escriba una prueba que lo llame y verifique el resultado esperado.
  3. Extraiga el método.

Si todo va bien, tendrá un método recién extraído con al menos una prueba de unidad de pasada.

Delphi's built in Extraer Método no le da ninguna forma de ajustar la firma, por lo que si esa es su opción, deberá hacerlo y solucionarlo después de la extracción. También querrá hacer público el nuevo método para que su prueba pueda acceder a él. Algunas personas se niegan a hacer público un método de utilidad privado, pero en esta etapa inicial no tiene muchas opciones. Una vez que haya progresado lo suficiente, comenzará a ver que algunos de los métodos de utilidad que ha extraído pertenecen a su propia clase (en cuyo caso deberían ser públicos de todos modos) mientras que otros pueden hacerse privados/protegidos y probados indirectamente. probando métodos que dependen de ellos.

A medida que su serie de pruebas crezca, querrá ejecutarlas después de cada cambio para asegurarse de que su último cambio no haya roto algo en otro lugar.

Este tema es demasiado grande para abarcar por completo en una respuesta. Descubrirá que la gran mayoría de sus preguntas están cubiertas cuando llega ese libro.

2

Yo diría que lo aborden en pequeños pasos enfocados.

Paso n. ° 1: Siempre debe hacerse algunas pruebas en su área de invasión TForm: pruebas de regresión también conocidas como red de seguridad. En tu caso, siente lo que está haciendo la aplicación. Por lo que leí, parece ser un transformador de datos.Por lo tanto, dedique un tiempo a comprender todas las combinaciones (o las más importantes, si no todas son factibles) de los datos de entrada y los programas de salida correspondientes. Escríbalos como pruebas. Asegúrese de que todas las pruebas pasen.

Paso n. ° 2: Ahora intente refactorizar. Mueva bloques de código en clases cohesivas, etc. todo bajo la seguridad de la red de regresión.

Prueba de conjuntos de datos complejos: la prueba de los volcados de archivos debe ser el último recurso. Pero en este caso, parece una opción simple para comenzar. Tal vez más adelante pueda convertirlo en un objeto de dominio de primera clase TSchedule con su propia implementación Equals(). Postergue las decisiones/cambios de diseño hasta que tenga un conjunto sólido de pruebas de regresión en su área de modificación.

+0

Ok gracias. El temor que tengo es que las pruebas de escritura tomarán mucho tiempo, porque los datos son complejos. Los volcados de archivos son complejos, así que siendo este mi primer intento de prueba unitaria en la vida real, me temo perder una semana y terminar con pruebas sin éxito. De todos modos, no tengo otro lugar para comenzar, quiero decir, necesito cambiar ESTE CÓDIGO y entonces debería comenzar desde aquí. – LaBracca

Cuestiones relacionadas