c# - proyecto - cuales son las mejores practicas para el desarrollo de software
Mejores prácticas de desarrollo controlado por prueba usando C#y RhinoMocks (7)
¡Este es un post 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 de TDD es fundamental.
TDD está altamente sujeto a la Ley del Retorno Marginal Decreciente . 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 primario de TDD está en el límite (blackbox), así como en pruebas de whitebox ocasionales de las áreas críticas de la misión del sistema.
Para ayudar a mi equipo a escribir un código comprobable, se me ocurrió esta sencilla lista de mejores prácticas para hacer que nuestra base de códigos 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?
Para maximizar la capacidad de prueba del código, siga estas reglas:
Escriba la prueba primero, luego el código. Motivo: Esto garantiza que se escriba un código comprobable y que cada línea de código obtenga pruebas escritas para ello.
Clases de diseño usando inyección de dependencia. Motivo: no se puede burlar ni probar lo que no se puede ver.
Separe el código de UI de su comportamiento usando 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).
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.
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.
Aislar dependencias externas. Motivo: las dependencias externas no resueltas no se pueden probar.
Marque como virtuales los métodos que pretende simular. Motivo: Rhino Mocks no puede simular métodos no virtuales.
Aquí hay otro en el que pensé que me gustaría hacer.
Si planea ejecutar pruebas de la prueba de la unidad Gui en lugar de TestDriven.Net o NAnt, me parece 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 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.
Buena lista. 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 que también querrías. 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 tiene un estilo más BDD . Esto me permite escribir pruebas para especificar qué debe hacer el método y qué debe hacer la aplicación en general.
Definitivamente una buena lista. Aquí hay algunas ideas sobre esto:
Escriba la prueba primero, luego el código.
Estoy de acuerdo, en un alto nivel. Pero, sería más específico: "Primero escribe una prueba, luego escribe el código justo 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 inyección de dependencia.
Convenido. 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 UI de su comportamiento usando Model-View-Controller o Model-View-Presenter.
Convenido. 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 de estar de acuerdo con este. 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 una razón 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 dependencias externas.
Convenido. 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 (también conocida como externa).
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 puntos nuevos 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.
Escriba pruebas usando la técnica Arrange / Act / Assert de Bill Wake . Esta técnica deja muy claro qué configuración es necesaria, qué se está probando en realidad y qué se espera.
No tengas miedo de rodar tus propios simuladores / talones. A menudo, descubrirá que usar 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 sus pruebas unitarias en clases base abstractas o métodos de instalación / desmontaje. Al hacerlo, se oculta el código de configuración / limpieza del desarrollador que intenta asimilar 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 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.)
La verdadera razón para programar contra las 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.
Sepa la diferencia entre falsificaciones, burlas y talones y cuándo usar cada uno.
Evite sobre especificar interacciones usando burlas. Esto hace que las pruebas sean brittle .
Si está trabajando con .Net 3.5, es posible que desee buscar en la biblioteca Moq Mocking: utiliza árboles de expresiones y lambdas para eliminar expresiones idiomáticas no intuitivas de la mayoría de las otras bibliotecas de burlas.
Consulte este quickstart para ver qué tan intuitivos son sus casos de prueba, aquí hay un ejemplo simple:
// 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));