2010-01-06 8 views
10

Me pregunto cómo debería probar este tipo de funcionalidad a través de NUnit.Cómo probar el código de la unidad que es altamente complejo detrás de la interfaz pública

Public void HighlyComplexCalculationOnAListOfHairyObjects() 
{ 
    // calls 19 private methods totalling ~1000 lines code + comments + whitespace 
} 

De la lectura veo que NUnit no está diseñado para poner a prueba los métodos privados por razones filosóficas sobre lo que la unidad de pruebas debe ser; pero sería casi imposible tratar de crear un conjunto de datos de prueba que ejecutaran por completo toda la funcionalidad involucrada en el cálculo. Mientras tanto, el cálculo se divide en una serie de métodos más pequeños que son razonablemente discretos. Sin embargo, no son cosas que tengan un sentido lógico para hacerse de forma independiente el uno del otro, por lo que están configuradas como privadas.

+3

Como otros han señalado: Si una clase es tan difícil de probar, a menudo es un signo de que la clase está tratando de hacer demasiado. Si el cálculo se puede dividir en métodos más pequeños, ¿es posible desglosarlo en etapas separadas, con cada etapa representada por su propia clase/interfaz? O bien, si el cálculo consiste principalmente en un grupo de fórmulas complejas, podría tener una clase especializada de "matemáticas" con un montón de métodos estáticos que implementen cada fórmula. – Aaronaught

+1

No sin un grado de reelaboración que está fuera del alcance/presupuesto/tiempo actual. –

+0

Dan - ¿qué terminaste haciendo? Estamos teniendo los mismos problemas. – Andrew

Respuesta

8

Has combinado dos cosas. La interfaz (que podría exponer muy poco) y esta clase de implementación en particular, que podría exponer mucho más.

  1. Define la interfaz más estrecha posible.

  2. Defina la clase Implementación con métodos y atributos comprobables (no privados). Está bien si la clase tiene cosas "extra".

  3. Todas las aplicaciones deben usar la interfaz y, en consecuencia, no tienen acceso de tipo seguro a las características expuestas de la clase.

¿Qué pasa si "alguien" pasa por alto la interfaz y utiliza la clase directamente? Son sociópatas; puedes ignorarlos con seguridad. No les proporcione soporte telefónico porque violaron la regla fundamental de usar la interfaz, no la implementación.

+1

Exponer los métodos solo para facilitar las pruebas no se siente bien. –

+0

@Dan Neely: (1) Es por eso que es un diseño * de prueba *. Tienes que hacerlo. Más importante. (2) Es por eso que Interface e Implementation están separados. La interfaz no expone nada. Y (3) En Python, no nos preocupamos demasiado por lo "privado" y estamos perfectamente felices y exitosos. "exposición" no, a la larga, cuenta mucho. –

+0

@S. Lott: pensamiento interesante, gracias. – Andrew

3

Para resolver su problema inmediato, puede consultar Pex, que es una herramienta de Microsoft Research que resuelve este tipo de problema al encontrar todos los valores límite relevantes para que se puedan ejecutar todas las rutas de código.

Dicho esto, si hubieras usado Desarrollo basado en pruebas (TDD), nunca te hubieras encontrado en esa situación, ya que habría sido casi imposible escribir pruebas unitarias que manejen este tipo de API.

Un método como el que describes parece tratar de hacer demasiadas cosas a la vez. Uno de los beneficios clave de TDD es que lo impulsa a implementar su código desde objetos pequeños y compostables en lugar de grandes clases con interfaces inflexibles.

+1

+1 para TDD. Como dice Mark, primero escribe pruebas y no terminarás aquí. – TrueWill

+0

Agua debajo del puente. El código en cuestión fue escrito mucho antes de que hubiera algún interés oficial en cualquier tipo de prueba automatizada en el equipo. –

+0

Pex se ve interesante, pero la versión de evaluación comercial requiere la versión de equipo de VS y todos en mi equipo solo tienen 2k8Pro. –

1

He visto (y probablemente escrito) muchos de esos objetos para el cabello. Si es difícil de probar, generalmente es un buen candidato para refactorizar. Por supuesto, un problema con eso es que el primer paso para refactorizar es asegurarse de que pase todas las pruebas primero.

Honestamente, sin embargo, me gustaría ver si hay alguna manera de dividir ese código en una sección más manejable.

0

Su pregunta implica que hay muchas rutas de ejecución en todo el subsistema. La primera idea que aparece en la mente es "refactor". Incluso si su API sigue siendo una interfaz de un solo método, las pruebas no deberían ser "imposibles".

2

Personalmente, haría que los métodos constituyentes sean internos, aplique InternalsVisibleTo y pruebe los diferentes bits.

Las pruebas de unidad de caja blanca aún pueden ser efectivas, aunque generalmente son más frágiles que las pruebas de caja negra (es decir,es más probable que tenga que cambiar las pruebas si cambia la implementación).

+0

El código ha existido y está en producción que, salvo que se descubran errores, es poco probable que se produzca un cambio en este punto. En el nivel de ondulación manual, el algoritmo está haciendo algo similar a la compresión con pérdida en la que, dependiendo de la implementación, hay más de un resultado posible para una entrada dada que sería "correcto" y que cualquier cambio importante. Como resultado, cualquier cambio importante podría romper las pruebas de blackbox también. –

2

HighlyComplexCalculationOnAListOfHairyObjects() es un olor de código, una indicación de que la clase que lo contiene está haciendo potencialmente demasiado y debe ser readaptado a través Extracto de Clase. Los métodos de esta nueva clase serían públicos y, por lo tanto, verificables como unidades.

Una cuestión a una refactorización este tipo es que la clase original llevó a cabo una gran cantidad de estado que tendría la nueva clase. Lo cual es otro olor a código, uno que indica que el estado debe moverse a un objeto de valor.

3

Como se ha mencionado, InternalsVisibleTo("AssemblyName") es un buen punto de partida cuando se prueba el código heredado.

Internal métodos siguen siendo privado en el sentido de que assemblys exterior del conjunto actual no pueden ver los métodos. Consulte MSDN para obtener más información.

Otra cosa sería que refactorizar el método de gran tamaño en las clases más pequeñas, más definidos. Verifique esta pregunta que hice sobre un problema similar, testing large methods.

0

tratando de crear un conjunto de datos de prueba que la ejecución íntegra de toda la funcionalidad involucrados en el cálculo sería casi imposible

Si eso es cierto, tratar una meta menos ambiciosa. Comience por probar rutas específicas de alto uso a través del código, rutas que sospeche que pueden ser frágiles y rutas para las que ha informado de errores.

Refactoring el método en sub-algoritmos separados hará que el código sea más comprobable (y podría ser beneficioso en otras formas), pero si su problema es un número ridícula de interacciones entre esos sub-algoritmos, método extracto (o extracto a la clase de estrategia) realmente no lo resolverá: tendrá que crear un conjunto sólido de pruebas de una en una.

+0

Hasta cierto punto, ya tenemos eso. Todos los casos comunes están cubiertos por un conjunto de archivos de prueba para una prueba de aceptación impulsada por humanos; y uno de mis compañeros de trabajo está (ab) usando NUnit para crear un conjunto de pruebas de nivel de integración que carga, procesa y examina el resultado de los archivos de prueba. Debido a la complejidad de lo que está sucediendo dentro del empuje principal, es probar los métodos internos por separado para asegurarnos de no habernos perdido ningún caso extremo. Por mis pecados (habiendo hecho el puerto VBA-> C# hace años), y tener la mejor idea de lo que está sucediendo dentro de la segunda parte me ha caído. –

1

conseguir el libro Working Effectively with Legacy Code por Michael plumas. Estoy cerca de un tercio del camino, y tiene múltiples técnicas para enfrentar este tipo de problemas.

+0

Segundo esto. Es un recurso excelente para construir islas de código probado de un código no probado y aparentemente no comprobable. Una vez que tenga suficientes pruebas en su lugar, podrá reparar y refactorizar la base de código existente. – Paddyslacker

Cuestiones relacionadas