c# - test - ¿Cómo debería una unidad probar un controlador.NET MVC?
unit test web api controller (6)
El objetivo de una prueba unitaria es probar el comportamiento de un método de forma aislada, en función de un conjunto de condiciones. Establece las condiciones de la prueba utilizando simulaciones y afirma el comportamiento del método comprobando cómo interactúa con otro código a su alrededor, verificando los métodos externos a los que intenta llamar, pero particularmente verificando el valor que devuelve dadas las condiciones.
Entonces, en el caso de los métodos del Controlador, que devuelven los Resultados de la Acción, es muy útil inspeccionar el valor del Resultado de la Acción devuelto.
Eche un vistazo a la sección ''Crear pruebas unitarias para controladores'' aquí para algunos ejemplos muy claros usando Moq.
Aquí hay una buena muestra de esa página que prueba que se devuelve una vista apropiada cuando el Controlador intenta crear un registro de contacto y falla.
[TestMethod]
public void CreateInvalidContact()
{
// Arrange
var contact = new Contact();
_service.Expect(s => s.CreateContact(contact)).Returns(false);
var controller = new ContactController(_service.Object);
// Act
var result = (ViewResult)controller.Create(contact);
// Assert
Assert.AreEqual("Create", result.ViewName);
}
Estoy buscando asesoramiento con respecto a la eficacia de las pruebas unitarias de los controladores. Mvc de .NET.
Donde trabajo, muchas de esas pruebas usan moq para burlarse de la capa de datos y afirmar que se llaman ciertos métodos de capa de datos. Esto no me parece útil, ya que básicamente verifica que la implementación no ha cambiado en lugar de probar la API.
También he leído artículos que recomiendan cosas como verificar que el tipo de modelo de vista devuelto sea el correcto. Puedo ver que proporciona algún valor, pero solo no parece merecer el esfuerzo de escribir muchas líneas de código de burla (el modelo de datos de nuestra aplicación es muy grande y complejo).
¿Alguien puede sugerir algunos enfoques mejores para las pruebas de unidades controladoras o explicar por qué los enfoques anteriores son válidos / útiles?
¡Gracias!
No veo mucho sentido en la prueba unitaria del controlador, ya que generalmente es solo un fragmento de código que conecta otras piezas. Las pruebas unitarias generalmente incluyen muchas burlas y simplemente verifica que los otros servicios estén conectados correctamente. La prueba en sí es un reflejo del código de implementación.
Prefiero las pruebas de integración: empiezo no con un controlador concreto, sino con una Url, y verifico que el Modelo devuelto tenga los valores correctos. Con la ayuda de Ivonna , la prueba podría verse así:
var response = new TestSession().Get("/Users/List");
Assert.IsInstanceOf<UserListModel>(response.Model);
var model = (UserListModel) response.Model;
Assert.AreEqual(1, model.Users.Count);
Puedo simular el acceso a la base de datos, pero prefiero un enfoque diferente: configurar una instancia en memoria de SQLite y volver a crearla con cada nueva prueba, junto con los datos requeridos. Hace que mis pruebas sean lo suficientemente rápidas, pero en lugar de burlas complicadas, las UserService
, por ejemplo, solo creo y UserService
una instancia de Usuario, en lugar de burlarme del UserService
(que podría ser un detalle de implementación).
Por lo general, cuando habla de pruebas unitarias, está probando un procedimiento o método individual, no un sistema completo, mientras trata de eliminar todas las dependencias externas.
En otras palabras, al probar el controlador, está escribiendo pruebas método por método y no debería tener siquiera que cargar la vista o el modelo, esas son las partes que debe "burlarse". A continuación, puede cambiar los simulacros para devolver valores o errores que son difíciles de reproducir en otras pruebas.
Primero debe poner sus controladores en una dieta. Entonces puedes divertirte probando la unidad. Si están gordos y has rellenado toda tu lógica de negocios dentro de ellos, estoy de acuerdo en que estarás pasando tu vida burlándose de tus pruebas unitarias y quejándote de que esto es una pérdida de tiempo.
Cuando se habla de lógica compleja, esto no significa necesariamente que esta lógica no se pueda separar en diferentes capas y que cada método se pruebe de forma aislada.
Sí, deberías probar todo el camino hasta la base de datos. El tiempo que le pones a la burla es menor y el valor que obtienes al burlarse es muy inferior (el 80% de los posibles errores en tu sistema no pueden ser recogidos mediante burlas).
Cuando prueba desde el controlador hasta la base de datos o el servicio web, entonces no se llama prueba unitaria sino prueba de integración. Personalmente creo en las pruebas de integración en lugar de las pruebas unitarias. Y puedo hacer un desarrollo impulsado por prueba con éxito.
Así es como funciona para nuestro equipo. Cada clase de prueba al principio regenera la base de datos y rellena / siembra las tablas con un conjunto mínimo de datos (por ejemplo, roles de usuario). Según la necesidad de un controlador, llenamos DB y verificamos si el controlador realiza su tarea. Esto está diseñado de tal manera que los datos corruptos de base de datos que dejan otros métodos nunca fallarán una prueba. Excepto que el tiempo lleve, casi todas las cualidades de la prueba unitaria (aunque es una teoría) son obtenibles.
Hubo solo un 2% de situaciones (o muy raras veces) en mi carrera cuando me vi obligado a usar simulaciones / resguardos ya que no era posible crear un origen de datos más realista. Pero en todas las demás situaciones, las pruebas de integración eran una posibilidad.
Nos tomó tiempo llegar a un nivel maduro con este enfoque. tenemos un buen marco que trata de la población y recuperación de datos de prueba (ciudadanos de primera clase). Y vale la pena el gran :) El primer paso es decir adiós a los simulacros y pruebas unitarias. ¡Si las burlas no tienen sentido, entonces no son para ti! La prueba de integración te da un buen sueño
================================
Editado después de un comentario a continuación: Demo
La prueba de integración o prueba funcional tiene que tratar directamente con DB. No se burla. Entonces estos son los pasos. Desea probar getEmployee () . todos estos 5 pasos a continuación se realizan en un único método de prueba.
- Drop DB
- Crear DB y rellenar roles y otros datos infra
- Crear un registro de empleado con ID
- Use esta identificación y llame a getEmployee ()
Ahora Assert () / Verificar si los datos devueltos son correctos
Esto demuestra que getEmployee () funciona. Los pasos hasta el 3 requieren que el código utilizado solo por el proyecto de prueba. El paso 4 llama al código de la aplicación. Lo que quise decir es que la creación de un empleado (paso 2) debe hacerse mediante el código del proyecto de prueba y no el código de la aplicación. Si existe un código de aplicación para crear un empleado (p. Ej .: CreateEmployee () ), entonces no se debe utilizar. Del mismo modo cuando probamos CreateEmployee () , el código de la aplicación GetEmployee () no debe utilizarse. Deberíamos tener un código de proyecto de prueba para obtener datos de una tabla.
¡De esta manera no hay burlas! La razón para descartar y crear DB es evitar que DB tenga datos incorrectos. Con nuestro enfoque, la prueba pasará sin importar cuántas veces la ejecutemos.
Consejo especial: en el paso 5 si getEmployee () devuelve un objeto de empleado. Si un desarrollador posterior elimina o cambia un nombre de campo, la prueba se rompe porque los campos están verificados. ¿Qué sucede si un desarrollador agrega un nuevo campo más adelante? Y él / ella olvida agregar una prueba para eso (afirmar)? La solución es agregar siempre una verificación de conteo de campo. por ejemplo: El objeto del empleado tiene 4 campos (Nombre, Apellido, Designación, Sexo). Así que el número de Assert de los campos del objeto empleado es 4. Y nuestra prueba fallará debido a la cuenta y le recordará al desarrollador que agregue un campo afirmar para el campo recién agregado. Y también nuestro código de prueba agregará este nuevo campo a DB y lo recuperará y verificará.
¡Y este es un gran artículo que discute los beneficios de las pruebas de integración sobre las pruebas unitarias porque "las pruebas unitarias matan!" (dice)
Una prueba de unidad controladora debe probar los algoritmos de código en sus métodos de acción, no en su capa de datos. Esta es una razón para burlarse de esos servicios de datos. El controlador espera recibir ciertos valores de repositorios / servicios / etc., y actuar de manera diferente cuando recibe información diferente de ellos.
Usted escribe pruebas unitarias para afirmar que el controlador se comporta de maneras muy específicas en escenarios / circunstancias muy específicas. Su capa de datos es una parte de la aplicación que proporciona esas circunstancias al controlador / métodos de acción. Afirmar que el controlador llamó a un método de servicio es valioso porque puede estar seguro de que el controlador obtiene la información de otro lugar.
Comprobar el tipo del modelo de vista devuelto es valioso porque, si se devuelve el tipo incorrecto de viewmodel, MVC arrojará una excepción de tiempo de ejecución. Puede evitar que esto suceda en producción ejecutando una prueba unitaria. Si la prueba falla, entonces la vista puede lanzar una excepción en producción.
Las pruebas unitarias pueden ser valiosas porque hacen que la refactorización sea mucho más fácil. Puede cambiar la implementación y afirmar que el comportamiento sigue siendo el mismo asegurándose de que todas las pruebas de la unidad pasen.
Respuesta al comentario n. ° 1
Si cambiar la implementación de un método bajo prueba requiere el cambio / remoción de un método burlado de capa inferior, entonces la prueba unitaria también debe cambiar. Sin embargo, esto no debería ocurrir tan a menudo como creas.
El flujo de trabajo de refactorización rojo-verde típico requiere la escritura de las pruebas unitarias antes de escribir los métodos que prueban. (Esto significa que durante un breve período de tiempo, su código de prueba no se compilará, y es por eso que muchos desarrolladores jóvenes / inexpertos tienen dificultades para adoptar el refactor verde rojo).
Si primero escribe las pruebas de su unidad, llegará a un punto en el que sabrá que el controlador necesita obtener información de una capa inferior. ¿Cómo puede estar seguro de que intenta obtener esa información? Burlando del método de capa inferior que proporciona la información, y afirmando que el método de capa inferior es invocado por el controlador.
Puede que me haya equivocado al usar el término "cambio de implementación". Cuando se debe modificar el método de acción de un controlador y la prueba de unidad correspondiente para cambiar o eliminar un método de burla, en realidad está cambiando el comportamiento del controlador. Refactorizar, por definición, significa cambiar la implementación sin alterar el comportamiento general y los resultados esperados.
Red-green-refactor es un enfoque de control de calidad que ayuda a prevenir errores y defectos en el código antes de que aparezcan. Normalmente, los desarrolladores cambian la implementación para eliminar errores después de que aparecen. Entonces, para reiterar, los casos que te preocupan no deberían suceder tan a menudo como crees.