2012-01-11 32 views
53

Estoy buscando asesoramiento con respecto a las pruebas efectivas de unidad de .NET mvc controladores.¿Cómo debería una unidad probar un controlador .NET MVC?

Donde trabajo, muchas de estas pruebas usan moq para simular la capa de datos y afirmar que se llaman a 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 mejores enfoques para las pruebas de unidad controladora o explicar por qué los enfoques anteriores son válidos/útiles?

Gracias!

Respuesta

42

Una prueba de unidad de controlador 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.

Escribe pruebas unitarias para afirmar que el controlador se comporta de forma muy específica en situaciones/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 de modelo de vista incorrecto, MVC emitirá 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 refabricació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 # 1

Si el cambio de la puesta en práctica de un llamadas bajo prueba método para el cambio/eliminación de un método burlado de capa inferior, entonces la unidad de prueba debe también cambiar. Sin embargo, esto no debería ocurrir tan a menudo como creas.

El típico flujo de trabajo de refactorización rojo-verde requiere la escritura de las pruebas de su unidad antes de escribiendo 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 escribe las pruebas de su unidad primero, llegará a un punto donde sabes 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.

Es posible que haya escrito mal cuando utilicé el término "cambio de implementación". Cuando el método de acción & de un controlador se debe modificar la prueba de unidad correspondiente para cambiar o eliminar un método simulado, realmente se 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 & 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.

+0

Estoy de acuerdo con mucho de lo que dices, pero no entiendo cómo la prueba unitaria puede ayudar cuando se cambia la aplicación (el último punto) si requiere que la implementación llame a ciertos métodos de nivel inferior (su segundo punto). – ChaseMedallion

+0

Todavía no estoy totalmente de acuerdo con la idea de probar que una función llama a otra función, pero entiendo de dónde vienes cuando lo piensas desde la perspectiva de "probar primero". Además, probablemente tenga razón en que, en muchos casos, un cambio en las llamadas de servicio indica un cambio en el comportamiento del controlador. ¡Gracias por la respuesta muy reflexiva, descriptiva y bien razonada! – ChaseMedallion

1

Por lo general, cuando habla de pruebas unitarias, está probando un procedimiento o método individual, no un sistema completo, mientras intenta 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.

9

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.

Por lo tanto, en el caso de los métodos del controlador, que devuelven resultados de acción, es muy útil para inspeccionar el valor del resultado de la acción devuelto.

Eche un vistazo a la sección 'Crear pruebas unitarias para controladores'here for some very clear examples 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); 
} 
7

no veo mucho sentido en la unidad de probar la controladora, ya que por lo general es sólo un pedazo 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 pruebas de integración - Empiezo no con un controlador concreto, pero con una Url, y verifico que el Modelo devuelto tiene los valores correctos. Con la ayuda de Ivonna, la prueba podría ser:

var response = new TestSession().Get("/Users/List"); 
Assert.IsInstanceOf<UserListModel>(response.Model); 

var model = (UserListModel) response.Model; 
Assert.AreEqual(1, model.Users.Count); 

que pueden burlarse del acceso a la base de datos, pero yo preferiría un enfoque diferente: configuración de una instancia en memoria de SQLite, y volver a crearla con cada nueva prueba, junto con los datos requeridos. Hace mis pruebas lo suficientemente rápido, pero en lugar de burlas complicadas, las aclaro, p. solo cree y guarde una instancia de Usuario, en lugar de simular el UserService (que podría ser un detalle de implementación).

+1

Creo que no entiende la idea detrás de las pruebas unitarias. Se supone que las pruebas unitarias prueban una pieza de código (unidad) separada de otras capas/dependencias, etc. En su caso, debe escribir pruebas separadas para las rutas MVC, la acción del controlador y el servicio. Esto que describió aquí son pruebas integrales, y una buena herramienta para ese tipo de pruebas es, p. SpecFlow y Watin. –

+1

Gracias, lo sé. Tenga en cuenta que escribí "Prefiero las pruebas de integración". WatiN no me deja investigar mi modelo, por ejemplo. – ulu

21

Primero debe poner sus controladores a dieta. Entonces puede have fun unidad probándolos. 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 habla de lógica compleja, esto no significa necesariamente que esta lógica no pueda separarse en diferentes capas y que cada método se pruebe de forma aislada.

+9

El enlace * puso sus controladores en una dieta * falta :( –

+2

Creo que este es el enlace que Darin estaba señalando: https://www.youtube.com/watch?v=8TYJjRxXnXs – ryanulit

+0

Pensé en editar y Cambié el enlace al suyo, gran trabajo @ryanulit! – Bart

6

Sí, debe 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 raramente) en mi carrera cuando me vi obligado a usar burlas/trozos, 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! prueba de integración que da buen sueño

===================================

Edited después de un comentario a continuación: Demo

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.

  1. gota DB
  2. Crear DB y rellenar papeles y otros datos infrarrojos
  3. Crear un registro de empleado con ID
  4. Utilice esta ID y llamar getEmployee()
  5. afirman ahora()/Verify 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. De la misma manera cuando probamos CreateEmployee() luego GetEmployee() código de aplicación no debería ser utilizado. 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 esto es un gran artículo sobre los beneficios de integration testing over unit testing

+1

Me gusta su enfoque. Prefiero las pruebas de integración también, ya que puede mostrar si el sistema funciona. La prueba de la unidad parece tratar solo con la parte lógica, que está bien pero no parece suficiente para mí. – Mariusz

+0

Es mejor probar la unidad de una base de datos y luego tener una prueba de integración simple en el proyecto de prueba mvc, o tiene una prueba de integración en el proyecto de prueba mvc que también prueba los datos devueltos? – Muflix

+2

sí. Prueba de integración o prueba funcional tiene que lidiar con DB directamente. No se burla. He editado la respuesta con una demostración de pasos. –

Cuestiones relacionadas