asp.net-mvc - español - mvc asp.net c#
¿Simplemente no entiendo las pruebas de unidad TDD(proyecto MVC de Asp.Net)? (3)
Estoy tratando de averiguar cómo realizar una prueba unitaria correcta y eficiente de mi proyecto Asp.net MVC. Cuando comencé con este proyecto, compré el Pro ASP.Net MVC, y con ese libro aprendí sobre TDD y las pruebas unitarias. Después de ver los ejemplos, y el hecho de que trabajo como ingeniero de software en control de calidad en mi empresa actual, me sorprendió lo increíble que parecía ser TDD. Así que comencé a trabajar en mi proyecto y empecé a escribir pruebas de unidad para mi capa de base de datos, capa de negocios y controladores. Todo tiene una prueba de unidad antes de la implementación. Al principio pensé que era increíble, pero luego las cosas empezaron a ir cuesta abajo.
Aquí están los problemas que empecé a encontrar:
Terminé escribiendo el código de la aplicación para hacer posible la realización de pruebas unitarias. No me refiero a esto de una buena manera ya que mi código estaba roto y tuve que arreglarlo para que la prueba de la unidad pasara. Quiero decir que es imposible abstraer la base de datos a una base de datos simulada debido al uso de linq para la recuperación de datos (utilizando el patrón de repositorio genérico).
La razón es que con linq-> sql o linq-> entidades puedes hacer uniones simplemente haciendo:
var objs = select p from _container.Projects select p.Objects;
Sin embargo, si se burla de la capa de la base de datos, para que esa línea pase la prueba de unidad, debe cambiarla.
var objs = select p from _container.Projects join o in _container.Objects on o.ProjectId equals p.Id select o;
Esto no solo significa que usted está cambiando la lógica de la aplicación para que pueda realizar una prueba unitaria, sino que también está haciendo que su código sea menos eficiente con el único propósito de probar, y eliminando muchas ventajas utilizando un ORM. .
Además, dado que muchos de los ID de mis modelos se generan en la base de datos, probé que tenía que escribir código adicional para manejar las pruebas que no son de la base de datos, ya que los ID nunca se generaron y aún tenía que manejar esos casos para que pasaran las pruebas unitarias, sin embargo, nunca ocurrirían en escenarios reales.
Así terminé tirando mi unidad de base de datos de pruebas.
Escribir pruebas unitarias para los controladores fue fácil, siempre y cuando devolviera las vistas. Sin embargo, la mayor parte de mi aplicación (y la que más se beneficiaría de las pruebas unitarias) es una aplicación web ajax complicada. Por diversas razones, decidí cambiar la aplicación de devolver las vistas a devolver JSON con los datos que necesitaba. Después de que esto ocurrió, mis pruebas de unidad se volvieron extremadamente dolorosas para escribir, ya que no he encontrado ninguna buena manera de escribir pruebas de unidad para json no trivial.
Después de golpearme la cabeza y perder una tonelada de tiempo tratando de encontrar una buena manera de probar la unidad JSON, desistí y eliminé todas las pruebas de la unidad de mi controlador (todas las acciones del controlador están enfocadas en esta parte de la aplicación hasta ahora).
Así que finalmente me quedé con la prueba de la capa de servicio (BLL). En este momento estoy usando EF4, sin embargo, también tuve este problema con linq-> sql. Elegí hacer el enfoque del modelo EF4 primero porque para mí, tiene sentido hacerlo de esa manera (defina mis objetos de negocio y deje que el marco descubra cómo traducirlo al backend de SQL). Esto estaba bien al principio, pero ahora se está volviendo engorroso debido a las relaciones.
Por ejemplo, digamos que tengo entidades de
Project
,User
yObject
. Un objeto debe estar asociado a un proyecto, y un proyecto debe estar asociado a un usuario. Esta no es solo una regla específica de la base de datos, también son mis reglas comerciales. Sin embargo, digamos que quiero hacer una prueba de unidad para poder guardar un objeto (para un ejemplo simple). Ahora tengo que hacer el siguiente código solo para asegurarme de que el guardado funcionó:User usr = new User { Name = "Me" }; _userService.SaveUser(usr); Project prj = new Project { Name = "Test Project", Owner = usr }; _projectService.SaveProject(prj); Object obj = new Object { Name = "Test Object" }; _objectService.SaveObject(obj); // Perform verifications
Hay muchos problemas con tener que hacer todo esto solo para realizar una prueba de unidad. Hay varios problemas con esto.
- Para empezar, si agrego una nueva dependencia, como todos los proyectos deben pertenecer a una categoría, debo ingresar a CADA prueba de unidad individual que haga referencia a un proyecto, agregar código para guardar la categoría y luego agregar código para agregar la categoría al proyecto. Esto puede ser un GRAN esfuerzo en el futuro para un cambio de lógica de negocios muy simple y, sin embargo, casi ninguna de las pruebas de unidad que modificaré para este requisito está realmente destinada a probar esa característica / requisito.
- Si luego agrego verificaciones a mi método SaveProject, de modo que los proyectos no se puedan guardar a menos que tengan un nombre con al menos 5 caracteres, entonces tengo que pasar por cada prueba unitaria de Objeto y Proyecto para asegurarme de que el nuevo requisito no cumpla. cualquier prueba de unidad no relacionada falla.
- Si hay un problema en el método
UserService.SaveUser()
, causará que todos los proyectos y las pruebas de la unidad de objetos fallen y la causa no se notará de inmediato sin tener que investigar las excepciones.
Por lo tanto, he eliminado todas las pruebas de unidad de capa de servicio de mi proyecto.
Podría seguir y seguir, pero hasta ahora no he visto ninguna manera de que las pruebas de unidad realmente me ayuden y no se interpongan en mi camino. Puedo ver casos específicos en los que puedo y probablemente implementaré pruebas unitarias, como asegurarme de que mis métodos de verificación de datos funcionen correctamente, pero esos casos son pocos y distantes entre sí. Algunos de mis problemas probablemente se pueden mitigar, pero no sin agregar capas adicionales a mi aplicación y, por lo tanto, hacer más puntos de falla solo para que pueda realizar una prueba unitaria.Por lo tanto no me quedan pruebas de unidad en mi código. Por suerte, uso mucho el control de código fuente para poder recuperarlos si lo necesito, pero no veo el punto.
En todas partes, en Internet, veo personas que hablan sobre lo excelentes que son las pruebas unitarias de TDD, y no solo estoy hablando de las personas fanáticas. Las pocas personas que descartan las pruebas de TDD / Unidad dan malos argumentos al afirmar que son más eficientes en la depuración a mano a través del IDE, o que sus habilidades de codificación son asombrosas y no lo necesitan. Reconozco que ambos de estos argumentos son un completo obstáculo, especialmente para un proyecto que debe ser mantenido por múltiples desarrolladores, pero cualquier refutación válida a TDD parece ser muy limitada.
Entonces, el punto de esta publicación es preguntar, ¿simplemente no entiendo cómo usar TDD y las pruebas unitarias automáticas?
En cuanto a la prueba de sus vistas AJAXified, le sugiero que pruebe un marco de prueba como WatiN .
Con esto puedes hacer lo siguiente (ejemplo desde su sitio).
[Test]
public void SearchForWatiNOnGoogle()
{
using (var browser = new IE("http://www.google.com"))
{
browser.TextField(Find.ByName("q")).TypeText("WatiN");
browser.Button(Find.ByName("btnG")).Click();
Assert.IsTrue(browser.ContainsText("WatiN"));
}
}
Parece que el problema no es con la prueba de la unidad, sino con su herramienta / plataforma. Vengo de un entorno Java, por lo que no tengo que volver a escribir mis consultas solo para satisfacer las pruebas unitarias.
Probar a Json es un dolor en el que sabes qué. Si no quiere probarlo, no lo haga;) La prueba no tiene una cobertura del 100%. Es probar las cosas que realmente necesitan ser probadas. Es mucho más probable que obtenga un error dentro de una expresión de complicación si que agregar cosas a un mapa y luego convertirlo a json.
Si prueba que el mapa se está creando correctamente ... hay una muy buena probabilidad de que el json también sea correcto. Por supuesto, no siempre lo será, pero sabrás que una vez que lo ejecutes y funciona. O no. Es realmente tan simple.
Sin embargo, puedo proporcionar un comentario real sobre el tercer problema. Realmente no quieres crear gráficos de objetos masivos. Pero hay algunas maneras de hacerlo.
Crear un singleton llamado ObjectMother. Tener métodos como ObjectMother.createProject (). En el interior, creas la instancia del proyecto ficticio. De esa manera, 12 pruebas pueden usar este método, y luego solo tienes que cambiarlo en un punto. Recuerde, el código de prueba debe ser refactorizado!
Además, puedes mirar algo como dbUnit. Bueno, eso es lo que se llama en el mundo de Java. La idea es que antes de ejecutar cada prueba, se coloca la base de datos en un estado conocido. Y lo hace automáticamente en la configuración / desmontaje de sus pruebas. Esto significa que su código de prueba puede comenzar a probar inmediatamente. Los datos reales de la prueba ficticia están en un script o archivo xml.
Espero que eso ayude.
Puede echar un vistazo a una estructura de proyecto MVC 2.0 de ASP.NET de muestra que escribí. Presenta algunos conceptos que podrían ayudarlo a comenzar a probar la lógica de su controlador y la base de datos. En lo que respecta a las pruebas de bases de datos, ya no son pruebas unitarias sino pruebas de integración. Como verá en mi muestra, estoy usando NHibernate, que me permite cambiar fácilmente a una base de datos de muestra SQLite que se vuelve a crear para cada dispositivo de prueba.
Por último, realizar pruebas unitarias en ASP.NET MVC podría ser una molestia sin una separación adecuada de preocupaciones y abstracciones, y usar marcos burlones y marcos como MVCContrib.TestHelper podría hacer su vida más fácil.
Aquí hay una vista previa de cómo podría verse la prueba de la unidad de control.
ACTUALIZAR:
Como respuesta a los comentarios, creo que la programación es una tarea concreta y es difícil dar una respuesta definitiva en cuanto a cómo puedo realizar una prueba unitaria de mi aplicación empresarial compleja. Para poder probar una aplicación compleja, el acoplamiento entre las capas debe ser lo más débil posible, lo que podría lograrse con interfaces y clases abstractas. Sin embargo, estoy de acuerdo en que lograr un acoplamiento tan débil en una aplicación compleja no es una tarea trivial.
Le podría dar un consejo: si todo el concepto de TDD es difícil de entender y no ve beneficios en ello, entonces está bien. Nadie puede probar que la TDD es beneficiosa en todas las situaciones. Solo intente diseñar su aplicación de tal manera que cada clase tenga una sola responsabilidad. Si se encuentra realizando una validación de entrada, acceso a datos SQL y manejo de excepciones en la misma clase, lo está haciendo mal. Una vez que logre esta separación, verá que las pruebas unitarias se vuelven mucho más fáciles e incluso podría llegar a una etapa en la que las pruebas unitarias impulsarán su desarrollo :-)
En cuanto a la prueba unitaria de un JsonResult
con MvcContrib.TestHelper
esta es una pregunta concreta a la que doy una respuesta concreta:
public class MyModel
{
public string MyProperty { get; set; }
}
public class HomeController : Controller
{
public ActionResult Index()
{
return Json(new MyModel { MyProperty = "value" });
}
}
Y la prueba:
[TestMethod]
public void HomeController_Index_Action_Should_Return_Json_Representation_Of_MyModel()
{
// arrange
var sut = new HomeController();
// act
var actual = sut.Index();
// assert
actual
.AssertResultIs<JsonResult>()
.Data
.ShouldBe<MyModel>("")
.MyProperty
.ShouldBe("value");
}