La mayoría de los tutoriales / ejemplos de pruebas unitarias que existen suelen incluir la definición de los datos que se probarán para cada prueba individual. Supongo que esto es parte de la teoría de "todo debe ser probado de forma aislada".
Sin embargo, descubrí que cuando se trata de aplicaciones de varios niveles con una gran cantidad de DI , el código requerido para configurar cada prueba se queda muy largo. En cambio, he creado una serie de clases testbase que ahora puedo heredar, que tiene muchos andamios de prueba preconstruidos.
Como parte de esto, también estoy creando conjuntos de datos falsos que representan la base de datos de una aplicación en ejecución, aunque generalmente con solo una o dos filas en cada "tabla".
¿Es una práctica aceptada predefinir, si no todos, la mayoría de los datos de prueba en todas las pruebas unitarias?
Actualizar
De los comentarios a continuación, parece que estoy haciendo más integración que pruebas unitarias.
Mi proyecto actual es ASP.NET MVC, que utiliza la Unidad de trabajo sobre Entity Framework Code First y Moq para las pruebas. Me he burlado de la UoW y los repositorios, pero estoy usando las clases de lógica de negocios reales y probando las acciones del controlador. Las pruebas a menudo verifican que la UoW se haya comprometido, por ejemplo:
[TestClass]
public class SetupControllerTests : SetupControllerTestBase {
[TestMethod]
public void UserInvite_ExistingUser_DoesntInsertNewUser() {
// Arrange
var model = new Mandy.App.Models.Setup.UserInvite() {
Email = userData.First().Email
};
// Act
setupController.UserInvite(model);
// Assert
mockUserSet.Verify(m => m.Add(It.IsAny<UserProfile>()), Times.Never);
mockUnitOfWork.Verify(m => m.Commit(), Times.Once);
}
}
SetupControllerTestBase
está construyendo el UoW simulado e instanciando el userLogic
.
Muchas de las pruebas requieren tener un usuario o producto existente en la base de datos, por lo que he completado previamente lo que devuelve el UoW simulado, en este ejemplo userData
, que es solo un IList<User>
registro de usuario único.
fuente
Respuestas:
En última instancia, desea escribir el menor código posible para obtener el mayor resultado posible. Tener mucho del mismo código en múltiples pruebas a) tiende a dar como resultado una codificación de copiar y pegar yb) significa que si cambia la firma de un método, puede terminar teniendo que arreglar muchas pruebas rotas.
Utilizo el enfoque de tener clases TestHelper estándar que me brindan muchos de los tipos de datos que uso habitualmente, para poder crear conjuntos de entidades estándar o clases DTO para que mis pruebas consulten y sepan exactamente qué obtendré cada vez. Entonces puedo llamar
TestHelper.GetFooRange( 0, 100 )
para obtener un rango de 100 objetos Foo con todas sus clases / campos dependientes establecidos.Particularmente donde hay relaciones complejas configuradas en un sistema de tipo ORM que necesitan estar presentes para que las cosas se ejecuten correctamente, pero no son necesariamente significativas para esta prueba que puede ahorrar mucho tiempo.
En situaciones en las que estoy probando cerca del nivel de datos, a veces creo una versión de prueba de mi clase de repositorio que se puede consultar de manera similar (una vez más, esto es en un entorno de tipo ORM, y no sería relevante para un base de datos real), porque burlarse de las respuestas exactas a las consultas es mucho trabajo y, a menudo, solo proporciona beneficios menores.
Hay algunas cosas a tener en cuenta, aunque en las pruebas unitarias:
fuente
Lo que sea que haga que la intención de su prueba sea más legible.
Como regla general:
Si los datos son parte de la prueba (p. Ej., No deben imprimir filas con un estado de 7), codifíquelos en la prueba para que quede claro lo que el autor pretendía que sucediera.
Si los datos son solo de relleno para asegurarse de que tienen algo con lo que trabajar (por ejemplo, no deberían marcar el registro como completo si el servicio de procesamiento arroja una excepción), entonces tenga un método BuildDummyData o una clase de prueba que mantenga los datos irrelevantes fuera de la prueba .
Pero tenga en cuenta que me cuesta pensar en un buen ejemplo de esto último. Si tiene muchos de estos en un dispositivo de prueba de unidad, probablemente tenga un problema diferente que resolver ... tal vez el método bajo prueba es demasiado complejo.
fuente
Diferentes métodos de prueba
Primero defina lo que está haciendo: Prueba de unidad o prueba de integración . El número de capas es irrelevante para las pruebas unitarias, ya que solo es probable que pruebe una clase. El resto te burlas. Para las pruebas de integración es inevitable que pruebe varias capas. Si tiene buenas pruebas unitarias, el truco es hacer que las pruebas de integración no sean demasiado complejas.
Si sus pruebas unitarias son buenas, no tiene que repetir las pruebas con todos los detalles al hacer las pruebas de integración.
Los términos que utilizamos dependen de la plataforma, pero puede encontrarlos en casi todas las plataformas de prueba / desarrollo:
Aplicación de ejemplo
Dependiendo de la tecnología que use, los nombres pueden diferir, pero lo usaré como ejemplo:
Si tiene una aplicación CRUD simple con el modelo de Producto, ProductsController y una vista de índice que genera una tabla HTML con productos:
El resultado final de la aplicación muestra una tabla HTML con una lista de todos los productos que están activos.
Examen de la unidad
Modelo
El modelo que puedes probar con bastante facilidad. Hay diferentes métodos para ello; Usamos accesorios. Creo que eso es lo que llamas "conjuntos de datos falsos". Entonces, antes de ejecutar cada prueba, creamos la tabla y colocamos los datos originales. La mayoría de las plataformas tienen métodos para esto. Por ejemplo, en su clase de prueba, un método setUp () que se ejecuta antes de cada prueba.
Luego ejecutamos nuestra prueba, por ejemplo: productos testGetAllActive .
Entonces probamos directamente a una base de datos de prueba. No nos burlamos de la fuente de datos; Lo hacemos siempre igual. Esto nos permite, por ejemplo, probar con una nueva versión de la base de datos, y surgirán problemas de consulta.
En el mundo real, no siempre se puede seguir el 100% de responsabilidad individual . Si desea hacerlo aún mejor, puede usar una fuente de datos de la que se burla. Para nosotros (usamos un ORM) que se siente como probar tecnología ya existente. Además, las pruebas se vuelven mucho más complejas y realmente no prueban las consultas. Entonces lo mantenemos de esta manera.
Los datos codificados se almacenan por separado en los dispositivos. Por lo tanto, el dispositivo es como un archivo SQL con una instrucción de creación de tabla e inserciones para los registros que utilizamos. Los mantenemos pequeños a menos que haya una necesidad real de realizar pruebas con muchos registros.
Controlador
El controlador necesita más trabajo, porque no queremos probar el modelo con él. Entonces, lo que hacemos es burlarnos del modelo. Eso significa: Probamos: método index () que debería devolver una lista de registros.
Así que nos burlamos del método de modelo getAllActive () y agregamos datos fijos (dos registros, por ejemplo). Ahora probamos los datos que el controlador envía a la vista y comparamos si realmente recuperamos esos dos registros.
Eso es suficiente. Intentamos agregar tan poca funcionalidad al controlador porque eso dificulta las pruebas. Pero, por supuesto, siempre hay algo de código en él. Por ejemplo, probamos requisitos como: Mostrar esos dos registros solo si ha iniciado sesión.
Por lo tanto, el controlador necesita un simulacro normalmente y una pequeña pieza de datos codificados. Para un sistema de inicio de sesión, tal vez otro. En nuestra prueba tenemos un método auxiliar para ello: setLoggedIn (). Eso simplifica la prueba con inicio de sesión o sin inicio de sesión.
Puntos de vista
Las pruebas de vistas son difíciles. Primero separamos la lógica que se repite. Lo ponemos en Helpers y probamos esas clases estrictamente. Esperamos siempre la misma salida. Por ejemplo, generateHtmlTableFromArray ().
Luego tenemos algunas vistas específicas del proyecto. No los probamos. Realmente no es deseable probarlos. Los guardamos para pruebas de integración. Debido a que eliminamos gran parte del código en las vistas, aquí tenemos un riesgo menor.
Si comienza a probarlos, es probable que necesite cambiar sus pruebas cada vez que cambie una pieza de HTML que no es útil para la mayoría de los proyectos.
Pruebas de integración
Dependiendo de su plataforma aquí, puede trabajar con historias de usuarios, etc. Puede estar basado en la web como Selenium u otras soluciones comparables.
En general, simplemente cargamos la base de datos con los dispositivos y afirmamos qué datos deberían estar disponibles. Para las pruebas de integración completa generalmente usamos requisitos muy globales. Entonces: configure el producto como activo y luego verifique si el producto está disponible.
No volvemos a probar todo, como si los campos correctos están disponibles. Probamos los requisitos más grandes aquí. Dado que no queremos duplicar nuestras pruebas desde el controlador o la vista. Si algo es realmente clave / parte central de su aplicación o por razones de seguridad (verifique que la contraseña NO esté disponible), las agregamos para asegurarnos de que sea correcta.
Los datos codificados se almacenan en los dispositivos.
fuente
Si está escribiendo pruebas que implican una gran cantidad de DI y cableado, hasta el uso de fuentes de datos "reales", probablemente abandonó el área de pruebas de unidades simples e ingresó al dominio de las pruebas de integración.
Para las pruebas de integración, creo, no es mala idea tener una lógica de configuración de datos común. El objetivo principal de tales pruebas es demostrar que todo está configurado correctamente. Esto es bastante independiente de los datos concretos enviados a través de su sistema.
Por otro lado, para las pruebas de Unidad, recomendaría mantener el objetivo de una clase de prueba en una sola clase "real" y burlarse de todo lo demás. Entonces, realmente debería codificar los datos de prueba para asegurarse de que cubrió tantas rutas de errores especiales / anteriores como sea posible.
Para agregar un elemento semi-codificado / aleatorio a las pruebas, me gusta presentar fábricas de modelos aleatorios. En una prueba que usa una instancia de mi modelo, luego uso estas fábricas para crear un objeto de modelo válido pero completamente aleatorio y luego codifico solo las propiedades que son de interés para la prueba en cuestión. De esta manera, usted especifica todos los datos relevantes directamente en su prueba, mientras le ahorra la necesidad de especificar también todos los datos irrelevantes y (hasta cierto punto) prueba de que no hay dependencias no deseadas en otros campos del modelo.
fuente
Creo que es bastante común codificar la mayoría de los datos para sus pruebas.
Considere una situación simple en la que un conjunto de datos en particular provoca un error. Puede crear específicamente una prueba unitaria para esos datos para ejercer la corrección y asegurarse de que el error no regrese. Con el tiempo, sus pruebas tendrán un conjunto de datos que cubren varios casos de prueba.
Los datos de prueba predefinidos también le permiten crear un conjunto de datos que cubre una amplia y conocida gama de situaciones.
Dicho esto, creo que también tiene valor tener algunos datos aleatorios en sus pruebas.
fuente