2008-09-23 6 views
86

Para ayudar a mi equipo a escribir código comprobable, se me ocurrió esta sencilla lista de mejores prácticas para hacer que nuestra base de código C# sea más comprobable. (Algunos de los puntos se refieren a las limitaciones de Rhino Mocks, un marco burlón para C#, pero las reglas también se pueden aplicar de manera más general). ¿Alguien tiene las mejores prácticas que siguen?Mejores prácticas de desarrollo controlado por prueba usando C# y RhinoMocks

Para maximizar la capacidad de prueba de código, siga estas reglas:

  1. Escribir la prueba primero, y luego el código. Motivo: Esto garantiza que se escriba un código comprobable y que cada línea de código tenga pruebas escritas para ello.

  2. Clases de diseño con inyección de dependencia. Motivo: No puede burlarse o probar lo que no se puede ver.

  3. Separe el código de la IU de su comportamiento utilizando Model-View-Controller o Model-View-Presenter. Motivo: permite probar la lógica comercial mientras se minimizan las partes que no se pueden probar (la IU).

  4. No escriba métodos o clases estáticos. Motivo: los métodos estáticos son difíciles o imposibles de aislar y Rhino Mocks no puede burlarse de ellos.

  5. Programa de interfaces, no de clases. Motivo: El uso de interfaces aclara las relaciones entre los objetos. Una interfaz debe definir un servicio que un objeto necesita de su entorno. Además, las interfaces se pueden burlar fácilmente utilizando Rhino Mocks y otros marcos de burla.

  6. Aislar dependencias externas. Motivo: las dependencias externas no resueltas no se pueden probar.

  7. Marque como virtuales los métodos que pretende simular. Motivo: Rhino Mocks no puede simular métodos no virtuales.

+0

Esta es una lista útil. Actualmente estamos usando NUnit y Rhino.Mocks, y es bueno deletrear estos criterios para los miembros del equipo que están menos familiarizados con este lado de las pruebas unitarias. –

Respuesta

56

Definitivamente una buena lista. Aquí hay algunos pensamientos al respecto:

Escriba primero la prueba y luego el código.

Estoy de acuerdo, en un alto nivel.Pero sería más específico: "Primero escribe una prueba y luego escribe con el código suficiente para pasar la prueba y repite". De lo contrario, me temo que las pruebas de mi unidad se parecerían más a las pruebas de integración o aceptación.

Clases de diseño usando la inyección de dependencia.

De acuerdo. Cuando un objeto crea sus propias dependencias, no tiene control sobre ellas. La inversión de Control/Dependency Injection le da ese control, lo que le permite aislar el objeto bajo prueba con mocks/stubs/etc. Así es como se prueban los objetos de forma aislada.

Separe el código de la IU de su comportamiento utilizando Model-View-Controller o Model-View-Presenter.

De acuerdo. Tenga en cuenta que incluso el presentador/controlador puede probarse usando DI/IoC, entregándole una vista y un modelo copiados/burlados. Consulte Presenter First TDD para obtener más información al respecto.

No escriba métodos o clases estáticos.

No estoy seguro Estoy de acuerdo con esto. Es posible probar un método/clase estático sin usar simulaciones. Entonces, quizás esta sea una de esas reglas específicas de Rhino Mock que mencionaste.

Programa de interfaces, no de clases.

Estoy de acuerdo, pero por un motivo ligeramente diferente. Las interfaces proporcionan una gran flexibilidad para el desarrollador de software, más allá del soporte para varios frameworks de objetos simulados. Por ejemplo, no es posible admitir DI correctamente sin interfaces.

Aislar las dependencias externas.

De acuerdo. Oculte dependencias externas detrás de su fachada o adaptador (según corresponda) con una interfaz. Esto le permitirá aislar su software de la dependencia externa, ya sea un servicio web, una cola, una base de datos u otra cosa. Esto es especialmente importante cuando su equipo no controla la dependencia (a.k.a. external).

Marque como virtuales los métodos que pretende simular.

Esa es una limitación de Rhino Mocks. En un entorno que prefiera trozos codificados a mano sobre un marco de objeto falso, eso no sería necesario.

Y, un par de nuevos puntos a considerar:

Use patrones de diseño creacional. Esto ayudará con DI, pero también le permite aislar ese código y probarlo independientemente de otra lógica.

Escribir pruebas usando Bill Wake's Arrange/Act/Assert technique. Esta técnica deja muy claro qué configuración es necesaria, qué se está probando realmente y qué se espera.

No tenga miedo de rodar sus propios simuladores. A menudo, descubrirá que el uso de frameworks de objetos falsos hace que sus pruebas sean increíblemente difíciles de leer. Al lanzar el suyo propio, tendrá control total sobre sus simulaciones/talones y podrá mantener sus pruebas legibles. (Refiérase al punto anterior.)

Evite la tentación de refactorizar la duplicación de las pruebas de su unidad en clases base abstractas o métodos de configuración/desmontaje. Al hacerlo, se oculta el código de configuración/limpieza del desarrollador que intenta realizar la prueba de la unidad. En este caso, la claridad de cada prueba individual es más importante que la refactorización de la duplicación.

Implementar la integración continua. Registre su código en cada "barra verde". Cree su software y ejecute su conjunto completo de pruebas unitarias en cada check-in. (Claro, esto no es una práctica de codificación, per se; pero es una herramienta increíble para mantener su software limpio y completamente integrado.)

+2

Normalmente encuentro que si una prueba es difícil de leer, no es culpa del marco sino del código que está probando. Si el SUT es complicado de configurar, entonces tal vez debería dividirse en más conceptos. –

1

Lista buena. Una de las cosas que tal vez quiera establecer, y no puedo darle muchos consejos, ya que estoy empezando a pensar en ello, es cuando una clase debe estar en una biblioteca diferente, espacio de nombres, espacios de nombres anidados. Incluso podría querer averiguar una lista de bibliotecas y espacios de nombres de antemano y exigir que el equipo se reúna y decida fusionar dos/agregar uno nuevo.

Oh, solo pensé en algo que hago y es posible que también quieras. Por lo general, tengo una biblioteca de pruebas de unidad con un accesorio de prueba por política de clase, donde cada prueba va a un espacio de nombres correspondiente. También tiendo a tener otra biblioteca de pruebas (¿pruebas de integración?) Que está en un número más BDD style. Esto me permite escribir pruebas para especificar qué debe hacer el método y qué debe hacer la aplicación en general.

+0

También hago una sección de prueba de estilo BDD similar (además del código de prueba de la unidad) en un proyecto personal. –

6

Conozca la diferencia entre fakes, mocks and stubs y cuándo usar cada uno.

Evite especificar interacciones utilizando simulaciones. Esto hace las pruebas brittle.

10

Si está trabajando con .Net 3.5, puede buscar en la biblioteca de burlas Moq - usa árboles de expresiones y lambdas para eliminar expresiones idiomáticas no intuitivas de la mayoría de las demás bibliotecas de burlas.

Control hacia fuera este quickstart para ver cuánto más intuitiva convierten en sus casos de prueba, aquí es un ejemplo sencillo:

// ShouldExpectMethodCallWithVariable 
int value = 5; 
var mock = new Mock<IFoo>(); 

mock.Expect(x => x.Duplicate(value)).Returns(() => value * 2); 

Assert.AreEqual(value * 2, mock.Object.Duplicate(value)); 
+5

Creo que la nueva versión de Rhino Mocks funciona así también –

0

Aquí hay un otro que pensé que me gusta hacer.

Si planea ejecutar pruebas de la prueba de la unidad Gui en lugar de TestDriven.Net o NAnt, entonces me ha resultado más fácil establecer el tipo de proyecto de prueba de la unidad en lugar de la biblioteca. Esto le permite ejecutar pruebas de forma manual y recorrerlas en modo de depuración (que TestDriven.Net puede hacer por usted).

Además, siempre me gusta tener un proyecto de Playground abierto para probar fragmentos de código e ideas con las que no estoy familiarizado. Esto no debe verificarse en el control de fuente. Aún mejor, debería estar en un repositorio de control de fuente separado en la máquina del desarrollador solamente.

3

¡Esta es una publicación muy útil!

Yo agregaría que siempre es importante entender el contexto y el sistema bajo prueba (SUT). Seguir los principios de TDD al pie de la letra es mucho más fácil cuando escribe código nuevo en un entorno donde el código existente sigue los mismos principios. Pero cuando estás escribiendo un código nuevo en un entorno heredado que no sea TDD, descubres que tus esfuerzos de TDD pueden ir mucho más allá de tus estimaciones y expectativas.

Para algunos de ustedes, que viven en un mundo completamente académico, los plazos y la entrega pueden no ser importantes, pero en un entorno donde el software es dinero, hacer un uso efectivo de su esfuerzo TDD es fundamental.

TDD está altamente sujeto a la Ley de Diminishing Marginal Return. En resumen, sus esfuerzos hacia TDD son cada vez más valiosos hasta que llegue a un punto de máximo rendimiento, después de lo cual, el tiempo posterior invertido en TDD tiene cada vez menos valor.

Tiendo a creer que el valor principal de TDD está en el límite (blackbox), así como en pruebas de whitebox ocasionales de áreas de misión crítica del sistema.

2

La verdadera razón para programar contra interfaces no es para facilitar la vida de Rhino, sino para aclarar las relaciones entre los objetos en el código. Una interfaz debe definir un servicio que un objeto necesita de su entorno. Una clase proporciona una implementación particular de ese servicio. Lea el libro "Diseño de Objetos" de Rebecca Wirfs-Brock sobre Roles, Responsabilidades y Colaboradores.

+0

De acuerdo ... Voy a actualizar mi pregunta para reflejar eso. –

Cuestiones relacionadas