2009-07-16 6 views
6

Estoy buscando algunos consejos para probar la unidad de manera que no se dejen "ganchos de prueba" en el código de producción.. Prueba de unidad .NET - Mejores prácticas para ocultar costuras de prueba en el código de versión

Digamos que tengo una clase estática (Módulo VB.NET) llamada MethodLogger que tiene un método "WriteEntry vacío (mensaje de cadena)" que está destinado a escribir el método de llamada y un mensaje a un archivo de registro en el disco. El objetivo real de WriteEntry() se puede desviar a una implementación falsa de IMethodLogger para ejecutar pruebas unitarias, y para todos los casos debe llamar a la implementación real de IMethodLogger.

Aquí hay un corte basto de lo que he azotado hasta ahora, y me gusta el enfoque - pero en algunas pruebas rápidas que plantea algunas preguntas y dudas:

[InternalAttributesVisibleTo("MethodLogger.Tests")] 
internal static class MethodLogger 
{ 
    public void WriteEntry(string message) 
    { 
     LogWriter.WriteEntry(message); 
    } 

    private IMethodLogger LogWriter 
    { 
     get; 
     set; 
    } 

#if DEBUG 
    internal void SetLogWriter(IMethodLogger logger) 
    { 
     LogWriter = logger; 
    } 
#endif 
} 

Aquí están mis preguntas específicas:

  • Esto estrecha las pruebas unitarias para ejecutar contra la compilación DEBUG; cuando realizo pruebas unitarias, aunque parece que no reconstruye específicamente el conjunto bajo prueba en DEPURACIÓN - ¿es posible hacerlo?

    • ¿Alguna vez quisiera ejecutar las pruebas de unidad contra una compilación sin depuración? En la parte superior de mi cabeza veo menos valor, pero ¿existiría?
  • ¿Sería posible para mí usar un indicador de precompilador personalizado como "PRUEBA" en lugar de "DEPURAR"? ¿Cómo voy a decirle a Visual Studio que siempre reconstruya el objetivo con esta bandera para probar para asegurar que mis ganchos/costuras estén disponibles?

+0

¿Ha considerado un marco AOP para esto? Como postrarrespira –

+0

@Chris S: No, en realidad es una nueva abreviatura para mí: un rápido vistazo a las definiciones me hace pensar un poco sobre el COI (que también soy muy verde). ¿Alguna distinción o diferencia particular entre los dos? – STW

Respuesta

3

No me gusta la idea de no poder ejecutar las pruebas contra los binarios de producción reales, personalmente.

¿No podría simplemente marcar al colocador como "obsoleto" y desactivar la advertencia obsoleta en su código de prueba? Junto con la documentación adecuada, esto debería evitar que el código de producción lo llame accidentalmente, pero aún así lo haga disponible para las pruebas.

Sin embargo, para responder a sus preguntas reales:

  • Sí, tiene sentido para ejecutar pruebas unitarias contra la falta de versiones de depuración - le da más confianza que cambia entre la depuración y construye no se rompen no depuración cualquier cosa.
  • Sí, puede usar su propio símbolo de preprocesador (como TEST) si lo desea; puede aplicar eso en las configuraciones existentes o crear una nueva configuración de compilación.
+1

Me hizo reír al pensar en una simple advertencia de IDE que impidiera que cualquiera de mis compañeros (excepto uno) hiciera una llamada. Estoy en un lugar sucio lleno de código incorrecto y pocas personas diligentes, Jon ... – STW

4

nos aseguramos de ejecutar las pruebas en contra de lanzamiento/ofuscado construye porque tenemos problemas a menudo al descubierto de esta manera, así que, sí, creo que hay valor para hacer que funcionen.

La ofuscación hace que las partes internas sean completamente inutilizables, lo cual es suficiente para mí, así que dejamos en métodos de prueba (y los hacemos internos).

En resumen, prefiero que se pruebe el código que preocuparse de que estén ahí, siempre y cuando no afecten el rendimiento.

2

Tener #IF DEBUG etc ... es mala forma.

El código de producción debe ser independiente del código de prueba/depuración.

Aconsejaría investigar la inyección de dependencia para ayudar en las pruebas unitarias.

+1

#IF es una directiva de preprocesador que ocurre antes de la compilación. si la condición no se cumple, ese código no va a terminar en el IL. –

+1

Ese no es el punto. Todavía está en el mismo archivo de código fuente que tu código de producción, el cual está equivocado. – Finglas

+2

Creo que Dockers sabe que # si DEBUG * es * ... él solo dice que no es una buena idea y que por lo general indica un olor codificado. Él está en lo correcto, IMO. – Randolpho

1

Es un método interno. Simplemente déjelo tal como está sin una compilación condicional, y solo llame al desde las pruebas de su unidad.

No hay nada de malo en dejar el gancho, de hecho, es posible que desee considerar hacer el método público y permitir que cualquier código cambie el gancho de registro.

Revisando su código nuevamente; ni siquiera necesita el método SetLogWriter; solo use un acceso privado para su clase estática y puede configurarlo usted mismo en las pruebas de su unidad.

puedo ver que es bien dejar el gancho en su lugar, la eliminación es sólo una preferencia, pero no estoy de acuerdo con lo que es pública - se siente demasiado a elevar los permisos sólo porque se elimina una pequeña molestia. - Yoooder

Bueno, comenté más sobre los principios generales de organización de códigos. En general, es una buena idea crear dichos ganchos para su código; en su caso, no estaría "elevando los permisos para eliminar una molestia", sino que estaría "diseñando un marco de registro robusto y flexible".

+0

Puedo ver que está bien dejar el gancho en su lugar, quitarlo es solo una preferencia, pero no estoy de acuerdo con hacerlo público: se siente como elevar los permisos solo porque elimina un poco la molestia. – STW

1

Esto me parece un candidato principal para un contenedor de IoC. Haría que mi registrador no sea estático y configuraría diferentes configuraciones para prueba, desarrollo y producción.

1

Su proceso de compilación debe incluir una compilación de producción con pruebas unitarias ejecutadas en su contra. Si no lo hace, no puede garantizar que alguien no se haya deslizado con pragmas (versión frente a depuración, por ejemplo).

Lo que siempre hago en el caso del registro es usar Log4Net. Durante el desarrollo y la prueba, los mensajes de registro se configuran al nivel más detallado y, en producción, tiendo a dejarlos en error o en el nivel de información, pero se configuran de forma más detallada para la solución de problemas, según sea necesario.

IMO la mejor manera de realizar lo que está tratando de lograr con SetLogWriter es utilizar un contenedor de inversión de control, como StructureMap, para asignar hormigones en tiempo de ejecución en función de varias configuraciones. Durante el tiempo de prueba unitaria puede usar una biblioteca como Moq para fabricar concretos que se comportan de manera esperada (una consulta SQL que siempre devuelve el mismo resultado) aunque en su caso particular está realizando exactamente lo contrario (registro deseado durante pruebas unitarias vs. . no se registra en el tiempo de ejecución de producción).

+0

Algunas pistas buenas para que investigue. Desafortunadamente para este caso en particular, MethodLogger ya está implementado y está en uso en todo el mundo ... – STW

2

De acuerdo con el libro "Trabajar de forma efectiva con el código heredado" de Michael Feathers, parece que sería mejor si nombrara al colocador algo específico para probar.

Ejemplo: SetTestingLogger (IMethodLogger logger)

I también eliminaría los estados del preprocesador.El libro también nos dice, cito

"Quizás se dejó un poco mareado por la idea de que va a quitar la protección de acceso cuando se utiliza colocador estática, pero recuerda que la finalidad de protección de acceso es previene errores . Estamos poniendo pruebas en para evitar errores también ".

Conclusión: parecería que lo mejor sería colocar estos ganchos en porque lo va a colocar por la razón de las pruebas y en última instancia, para eliminar errores.

La sección "Introducir al instalador estático" en el libro explica esto con más detalle. Lo recomendaría como un libro que todos los desarrolladores deberían tener.

+0

Me gusta el código de autodescripción no ambiguo :) – STW

+0

+1 para Feathers! ¡Gran libro! – TrueWill

4

En realidad, usted está en el camino hacia la inyección de dependencias - que simplemente no se han dado cuenta todavía :)

No hay nada de malo en dejar las costuras en su lugar - sólo hay que modelarlos para que en vez de ser específicos de la prueba, en realidad son mejoras de su API. Esto requiere un poco de práctica, pero antes que nada un cambio de mentalidad.

Escribí sobre esto no hace mucho en Testability Is Really The Open/Closed Principle.

En su caso específico, hay un par de cambios que debe hacer a la clase MethodLogger:

  1. lo convierten en un ejemplo de clase en lugar de una clase estática. Como todos los practicantes de TDD con experiencia le dirán, la estática es evil.
  2. Deje que el constructor único tome una instancia de IMethodLogger. Esto se llama Constructor Injection y obliga a todos los clientes a tomar una decisión explícita sobre cómo iniciar sesión.
  3. Configure el gráfico de dependencias manualmente o use un contenedor DI para conectar el MethodLogger con el IMethodLogger deseado.
+1

He oído que se debe evitar la estática, pero a mi mente siempre le ha costado encontrar una mejor alternativa para ellos si se los usa (con relativa) responsabilidad como ayudantes sin estado. Asumiendo que tengo mi clase de MethodLogger en todo el sistema (básicamente lo hacemos) ¿hasta qué punto sugiere su conversión a una clase de instancia sin dejar de permitir que todos sus consumidores trabajen con ella sin tener que configurar/desmontar su propia copia privada? – STW

+0

@STW - La sugerencia de Mark de un contenedor DI (framework) le permitiría convertirlo a una clase de instancia pero (opcionalmente) proporcionar la misma instancia a cada consumidor. – TrueWill

0

¿Sería posible que yo utilizo una bandera costumbre pre-compilador como "TEST" en lugar de "debug"? ¿Cómo voy a para decirle a Visual Studio que siempre reconstruya el objetivo con esta bandera para las pruebas para asegurar que mis ganchos/costuras estén disponibles ?

Sí. Lo que quiere hacer es agregar un símbolo de compilación condicional. Puede hacerlo en las propiedades del proyecto en la pestaña Construir. Puede crear una configuración de compilación diferente en Configuration Manager y agregar solo su símbolo TEST a la nueva configuración.

2
  1. como otros han dicho, avanzar hacia la inyección de dependencias y la inversión de control (COI)
  2. aislar clases bajo prueba, así, con la ayuda de marcos de aislamiento (Moq, etc.)
  3. pruebas de movimiento en un ensamblado independiente - sin necesidad de DEBUG #if ... #endif
4

Usted podría utilizar Typemock aislador para eludir cualquier problema de diseño que perjudican la capacidad de prueba. a diferencia de otros marcos, también trabajará con métodos estáticos, métodos privados y métodos no virtuales. a medida que aprende a un mejor diseño, su código será más "comprobable" por defecto, pero si desea obtener sólo en torno a estas cuestiones ahora, esto ayudará

http://blog.typemock.com/2009/01/isolator-new-vbnet-friendly-api.html

(Además, es el único herramienta con una API compatible con VB)

Cuestiones relacionadas