unit testing - mock - Patrones de diseño útiles para pruebas unitarias/TDD?
mock pruebas unitarias (9)
La lectura de esta pregunta me ayudó a solidificar algunos de los problemas que siempre he tenido con las pruebas unitarias, TDD, et al.
Desde que encontré el enfoque TDD para el desarrollo, sabía que era el camino correcto a seguir. La lectura de varios tutoriales me ayudó a entender cómo comenzar, pero siempre han sido muy simplistas, no realmente algo que se pueda aplicar a un proyecto activo. Lo mejor que he logrado es escribir pruebas en partes pequeñas de mi código, cosas como bibliotecas, que son utilizadas por la aplicación principal pero que no están integradas de ninguna manera. Si bien esto ha sido útil, equivale a aproximadamente el 5% de la base de código. Hay muy poco por ahí sobre cómo pasar al siguiente paso, para ayudarme a obtener algunas pruebas en la aplicación principal.
Comentarios como " La mayoría del código sin pruebas unitarias está construido con dependencias duras (es decir, nuevas en todos lados) o métodos estáticos " y " ... no es raro tener un alto nivel de acoplamiento entre clases, difíciles de configurar objetos dentro de su clase, [...] etc. "me han hecho darme cuenta de que el siguiente paso es comprender cómo desvincular el código para hacerlo comprobable.
¿Qué debería estar mirando para ayudarme a hacer esto? ¿Existe un conjunto específico de patrones de diseño que necesito comprender y comenzar a implementar para facilitar las pruebas?
Me han hecho darme cuenta de que el siguiente paso es comprender cómo desvincular el código para que sea comprobable.
¿Qué debería estar mirando para ayudarme a hacer esto? ¿Existe un conjunto específico de patrones de diseño que necesito comprender y comenzar a implementar para facilitar las pruebas?
¡Tocar el asunto exacto! SOLID es lo que estás buscando (sí, realmente). Sigo recomendando estos 2 libros electrónicos , especialmente el de SOLID para el tema en cuestión.
También debes entender que es muy difícil si estás presentando pruebas unitarias a una base de códigos existente. Lamentablemente, el código estrechamente acoplado es demasiado común. Esto no significa que no lo hagas, pero para pasarlo bien será como dijiste, las pruebas se concentrarán más en pequeños fragmentos.
Con el tiempo, se convierten en un ámbito más amplio, pero depende del tamaño de la base de códigos existente, del tamaño del equipo y de la cantidad de personas que lo están haciendo en lugar de aumentar el problema.
Aquí, Mike Clifton describe 24 patrones de prueba a partir de 2004. Es una heurística útil cuando se diseñan pruebas unitarias.
http://www.codeproject.com/Articles/5772/Advanced-Unit-Test-Part-V-Unit-Test-Patterns
Patrones de aprobado / reprobado
Estos patrones son su primera línea de defensa (o ataque, según su perspectiva) para garantizar un buen código. Pero ten cuidado, son engañosos en lo que te dicen sobre el código.
- El patrón de prueba simple
- El patrón de ruta de código
- El patrón de rango de parámetros
Patrones de transacciones de datos
Los patrones de transacción de datos son un comienzo para abrazar los problemas de la persistencia y comunicación de datos. Más sobre este tema se discute en "Patrones de simulación". Además, estos patrones omiten intencionadamente las pruebas de estrés, por ejemplo, la carga en el servidor. Esto se discutirá en "Patrones de prueba de estrés".
- El patrón Simple-Data-I / O
- El patrón de restricción de datos
- El patrón de revertir
Patrones de gestión de colecciones
Mucho de lo que hacen las aplicaciones es administrar colecciones de información. Si bien hay una variedad de colecciones disponibles para el programador, es importante verificar (y documentar) que el código está utilizando la colección correcta. Esto afecta el orden y las restricciones.
- El patrón de orden de colección
- El patrón de enumeración
- Patrón de Restricción de Colección
- El patrón de indexación de la colección
Patrones de rendimiento
Las pruebas unitarias no deberían preocuparse únicamente por la función sino también por la forma. ¿Qué tan eficientemente el código bajo prueba realiza su función? ¿Qué rápido? ¿Cuánta memoria usa? ¿Compensa la inserción de datos para la recuperación de datos de manera efectiva? ¿Libera recursos correctamente? Estas son todas las cosas que están bajo el control de pruebas unitarias. Al incluir los patrones de rendimiento en la prueba unitaria, el implementador tiene un objetivo que alcanzar, lo que se traduce en un mejor código, una mejor aplicación y un cliente más feliz.
- El patrón de prueba de rendimiento
Patrones de proceso
Las pruebas unitarias están destinadas a probar, bueno, unidades ... las funciones básicas de la aplicación. Se puede argumentar que los procesos de prueba deben ser relegados a los procedimientos de prueba de aceptación, sin embargo, no creo en este argumento. Un proceso es simplemente un tipo diferente de unidad. Los procesos de prueba con un probador unitario brindan las mismas ventajas que otras pruebas unitarias: documenta la forma en que el proceso debe funcionar y el probador unitario puede ayudar al implementador probando también el proceso fuera de secuencia, identificando rápidamente posibles problemas de interfaz de usuario como bien. El término "proceso" también incluye transiciones de estado y reglas comerciales, ambas deben validarse.
- El patrón de secuencia de proceso
- El patrón de estado de proceso
- El patrón de regla de proceso
Patrones de simulación
Las transacciones de datos son difíciles de probar porque a menudo requieren una configuración preestablecida, una conexión abierta y / o un dispositivo en línea (por nombrar algunos). Los objetos falsos pueden acudir al rescate simulando la base de datos, el servicio web, el evento del usuario, la conexión y / o el hardware con el que el código realiza transacciones. Los objetos simulados también tienen la capacidad de crear condiciones de falla que son muy difíciles de reproducir en el mundo real: una conexión con pérdida, un servidor lento, un centro de red fallido, etc.
- Patrón de simulacro de objeto
- El patrón de simulación de servicio
- El patrón de simulación de error de bit
- El patrón de simulación de componentes
Patrones de subprocesamiento múltiple
La prueba unitaria de aplicaciones multiproceso es probablemente una de las cosas más difíciles de hacer porque debe establecer una condición que, por su propia naturaleza, pretende ser asincrónica y, por lo tanto, no determinista. Este tema es probablemente un artículo importante en sí mismo, por lo que proporcionaré solo un patrón muy genérico aquí. Además, para realizar muchas pruebas de enhebrado correctamente, la aplicación del comprobador de unidades debe ejecutar pruebas por separado para que el comprobador de unidades no se deshabilite cuando un hilo termina en estado de espera.
- El patrón señalado
- El patrón de resolución de interbloqueo
Patrones de prueba de estrés
La mayoría de las aplicaciones se prueban en entornos ideales: el programador usa una máquina rápida con poco tráfico de red y usa pequeños conjuntos de datos. El mundo real es muy diferente. Antes de que algo se rompa por completo, la aplicación puede sufrir degradación y responder mal o con errores al usuario. Las pruebas unitarias que verifican el rendimiento del código bajo estrés se deben cumplir con el mismo fervor (si no más) que las pruebas unitarias en un entorno ideal.
- El patrón Bulk-Data-Stress-Test
- El patrón de prueba de estrés de recursos
- El patrón de prueba de carga
Patrones de capas de presentación
Uno de los aspectos más desafiantes de las pruebas unitarias es verificar que la información llega al usuario directamente en la capa de presentación y que el funcionamiento interno de la aplicación establece correctamente el estado de la capa de presentación. A menudo, las capas de presentación están enredadas con objetos comerciales, objetos de datos y lógica de control. Si está planeando probar la unidad de la capa de presentación, debe darse cuenta de que una separación clara de las preocupaciones es obligatoria. Parte de la solución implica el desarrollo de una arquitectura Modelo-Vista-Controlador (MVC) apropiada. La arquitectura MVC proporciona un medio para desarrollar buenas prácticas de diseño cuando se trabaja con la capa de presentación. Sin embargo, es fácilmente abusado. Se requiere una cierta cantidad de disciplina para asegurarse de que, de hecho, está implementando la arquitectura MVC correctamente, en lugar de solo en palabras.
- El patrón de prueba de estado de vista
- El patrón de prueba de modelo de estado
Diría que necesitas principalmente dos cosas para probar, y van de la mano:
- Interfaces, interfaces, interfaces
- inyección de dependencia; esto, junto con las interfaces, le ayudará a intercambiar piezas a voluntad para aislar los módulos que desea probar. ¿Desea probar su sistema tipo cron que envía notificaciones a otros servicios? ejemplifíquelo y sustituya su implementación de código real para todo lo demás por componentes que obedezcan a la interfaz correcta, pero que estén configurados para reaccionar de la forma que desee probar: ¿notificación por correo? probar lo que sucede cuando el servidor SMTP está inactivo lanzando una excepción
Yo mismo no he dominado el arte de las pruebas unitarias (y estoy lejos de eso), pero aquí es donde mis principales esfuerzos van actualmente. El problema es que todavía no diseño para las pruebas, y como resultado mi código tiene que inclinarse hacia atrás para acomodar ...
El libro de Michael Feather Working Effectively With Legacy Code es exactamente lo que estás buscando. Él define el código heredado como ''código sin pruebas'' y habla sobre cómo ponerlo bajo prueba.
Como con la mayoría de las cosas, es un paso a la vez. Cuando realiza un cambio o una solución, intente aumentar la cobertura de la prueba. A medida que pase el tiempo, tendrás un conjunto de pruebas más completo. Habla sobre las técnicas para reducir el acoplamiento y cómo ajustar las piezas de prueba entre la lógica de la aplicación.
Como se señaló en otras respuestas, la inyección de dependencia es una buena forma de escribir código comprobable (y débilmente acoplado en general).
Inyección de Dependencia / IoC. Lea también sobre los marcos de inyección de dependencia como SpringFramework y google-guice. También se enfocan en cómo escribir código comprobable.
Los patrones de diseño no son directamente relevantes para TDD, ya que son detalles de implementación. No intentes ajustar patrones en tu código solo porque existan, sino que tienden a aparecer a medida que tu código evoluciona. También se vuelven útiles si su código es maloliente, ya que ayudan a resolver estos problemas. No desarrolles código con patrones de diseño en mente, solo escribe el código. Luego, pasa el examen y refactoriza.
Muchos problemas como este se pueden resolver con la encapsulación adecuada. O bien, puede tener este problema si está mezclando sus preocupaciones. Supongamos que tiene un código que valida a un usuario, valida un objeto de dominio y luego guarda el objeto de dominio en un solo método o clase. Has mezclado tus preocupaciones y no serás feliz. Debe separar esas preocupaciones (autenticación / autorización, lógica comercial, persistencia) para que pueda probarlas de forma aislada.
Los patrones de diseño ayudan, pero muchos de los exóticos tienen problemas muy estrechos a los que se pueden aplicar. Patrones como compuesto, comando, se usan a menudo y son simples.
La directriz es: si es muy difícil probar algo, probablemente pueda refactorizarlo en problemas más pequeños y probar los bits reajustados de forma aislada. Entonces, si tienes un método de 200 líneas con 5 niveles de sentencias if y algunos for-loops, es posible que desees romper ese bombeo.
Entonces, comience por ver si puede simplificar el código complicado separando sus preocupaciones, y luego vea si puede simplificar el código complicado dividiéndolo. Por supuesto, si un patrón de diseño salta hacia ti, entonces ve por él.
Patrones de prueba xUnit de Gerard Meszaros: Refactoring Test Code está repleto de patrones para pruebas unitarias. Sé que estás buscando patrones en TDD, pero creo que encontrarás mucho material útil en este libro
El libro está en safari para que puedas echarle un vistazo a lo que hay dentro para ver si puede ser útil: http://my.safaribooksonline.com/9780131495050
Arrange, Act, Assert es un buen ejemplo de un patrón que le ayuda a estructurar su código de prueba en casos de uso particulares.
Aquí hay un código hipotético de C # que demuestra el patrón.
[TestFixture]
public class TestSomeUseCases() {
// Service we want to test
private TestableServiceImplementation service;
// IoC-injected mock of service that''s needed for TestableServiceImplementation
private Mock<ISomeService> dependencyMock;
public void Arrange() {
// Create a mock of auxiliary service
dependencyMock = new Mock<ISomeService>();
dependencyMock.Setup(s => s.GetFirstNumber(It.IsAny<int>)).Return(1);
// Create a tested service and inject the mock instance
service = new TestableServiceImplementation(dependencyMock.Object);
}
public void Act() {
service.ProcessFirstNumber();
}
[SetUp]
public void Setup() {
Arrange();
Act();
}
[Test]
public void Assert_That_First_Number_Was_Processed() {
dependencyMock.Verify(d => d.GetFirstNumber(It.IsAny<int>()), Times.Exactly(1));
}
}
Si tiene muchos escenarios para probar, puede extraer una clase abstracta común con bits concretos Arrange & Act (o simplemente Organizar) e implementar los bits abstractos restantes y las funciones de prueba en las clases heredadas que agrupan las funciones de prueba.