ASP.NET MVC3 y la primera architecture del Entity Framework Code

Mi pregunta anterior me hizo pensar nuevamente sobre capas, repository, dependency injection y cosas arquitectónicas como esta.

Mi architecture ahora se ve así:
Estoy usando código EF primero, así que acabo de hacer clases POCO y contexto. Eso crea db y modelo.
Nivel más alto son las clases de capa de negocios (proveedores). Estoy usando un proveedor diferente para cada dominio … como MemberProvider, RoleProvider, TaskProvider, etc. y estoy creando una nueva instancia de mi DbContext en cada uno de estos proveedores.
Luego instanciar estos proveedores en mis controladores, obtener datos y enviarlos a Vistas.

Mi architecture inicial incluía repository, del cual me deshice porque me dijeron que solo agrega complejidad, entonces ¿por qué no uso solo EF? Quería hacerlo … trabajando con EF directamente desde los controladores, pero tengo que escribir pruebas y fue un poco complicado con una base de datos real. Tuve que fingir, burlar los datos de alguna manera. Así que hice una interfaz para cada proveedor e hice proveedores falsos con datos codificados en listas. Y con esto volví a algo, donde no estoy seguro de cómo proceder correctamente.

Estas cosas comienzan a complicarse demasiado rápido … muchos enfoques y “patrones” … crean demasiado ruido y un código inútil.

¿Existe alguna architecture SIMPLE y comprobable para crear y la aplicación ASP.NET MVC3 con Entity Framework?

Si desea utilizar TDD (o cualquier otro enfoque de prueba con cobertura de prueba alta) y EF juntos, debe escribir pruebas de integración o de extremo a extremo. El problema aquí es que cualquier enfoque con burla de contexto o repository solo crea una prueba que puede probar la lógica de capa superior (que usa esos simulacros) pero no su aplicación.

Ejemplo simple:

Definamos el repository genérico:

public interface IGenericRepository { IQueryable GetQuery(); ... } 

Y vamos a escribir algún método de negocios:

 public IEnumerable DoSomethingImportant() { var data = MyEntityRepo.GetQuery().Select((e, i) => e); ... } 

Ahora, si se burla del repository, usará Linq-To-Objects y tendrá una prueba verde, pero si ejecuta la aplicación con Linq-To-Entities obtendrá una excepción porque seleccionar sobrecarga con índices no es compatible con L2E.

Este fue un ejemplo simple, pero puede ocurrir lo mismo con el uso de métodos en consultas y otros errores comunes. Además, esto también afecta a métodos como Agregar, Actualizar, Eliminar generalmente expuestos en el repository. Si no escribe un simulacro que simule exactamente el comportamiento del contexto EF y la integridad referencial, no pondrá a prueba su implementación.

Otra parte de la historia son los problemas con la carga diferida que tampoco se puede detectar con pruebas unitarias contra los simulacros.

Por eso, también debe introducir pruebas de integración o de extremo a extremo que funcionen contra una base de datos real utilizando el contexto EF real ane L2E. Por cierto. es necesario utilizar pruebas de extremo a extremo para usar TDD correctamente. Para escribir pruebas de extremo a extremo en ASP.NET MVC, puede usar WatiN y posiblemente también SpecFlow para BDD, pero esto realmente agregará mucho trabajo, pero tendrá su aplicación realmente probada. Si quieres leer más sobre TDD, recomiendo este libro (la única desventaja es que los ejemplos están en Java).

Las pruebas de integración tienen sentido si no utiliza el repository genérico y oculta sus consultas en alguna clase que no exponga IQueryable sino que devuelva datos directamente.

Ejemplo:

 public interface IMyEntityRepository { MyEntity GetById(int id); MyEntity GetByName(string name); } 

Ahora puede simplemente escribir la prueba de integración para probar la implementación de este repository porque las consultas están ocultas en esta clase y no están expuestas a las capas superiores. Pero este tipo de repository se considera de alguna manera como una implementación antigua utilizada con procedimientos almacenados. Perderá muchas características de ORM con esta implementación o tendrá que hacer un montón de trabajo adicional, por ejemplo, agregar un patrón de especificación para poder definir la consulta en la capa superior.

En ASP.NET MVC, puede reemplazar parcialmente las pruebas de extremo a extremo con pruebas de integración en el nivel de controlador.

Editar basado en el comentario:

No digo que necesite pruebas unitarias, pruebas de integración y pruebas de extremo a extremo. Digo que hacer aplicaciones probadas requiere mucho más esfuerzo. La cantidad y tipos de pruebas necesarias dependen de la complejidad de su aplicación, el futuro esperado de la aplicación, sus habilidades y habilidades de otros miembros del equipo.

Se pueden crear pequeños proyectos directos sin pruebas en absoluto (vale, no es una buena idea, pero todos lo hicimos y al final funcionó) pero una vez que un proyecto pasa un umbral puedes encontrar que introducir nuevas características o mantener el proyecto es muy difícil porque nunca está seguro si rompe algo que ya funcionó, eso se llama regresión. La mejor defensa contra la regresión es un buen conjunto de pruebas automatizadas.

  • Las pruebas unitarias lo ayudan a probar el método. Tales pruebas idealmente deberían cubrir todas las rutas de ejecución en el método. Estas pruebas deben ser muy breves y fáciles de escribir; una parte complicada puede ser configurar dependencias (mocks, faktes, stubs).
  • Las pruebas de integración lo ayudan a probar la funcionalidad en múltiples capas y generalmente a través de múltiples procesos (aplicación, base de datos). No necesita tenerlos para todo, se trata más de experiencia para seleccionar dónde son útiles.
  • Las pruebas de extremo a extremo son algo así como la validación del caso de uso / historia / función del usuario. Deben cubrir todo el flujo del requisito.

No es necesario probar una función varias veces: si sabe que la característica se prueba en una prueba integral, no necesita escribir una prueba de integración para el mismo código. Además, si sabe que el método tiene solo una ruta de ejecución única que está cubierta por la prueba de integración, no necesita escribir una prueba de unidad para ella. Esto funciona mucho mejor con el enfoque TDD donde se comienza con una gran prueba (extremo a extremo o integración) y se profundiza en las pruebas unitarias.

Dependiendo de su enfoque de desarrollo, no tiene que comenzar con varios tipos de prueba desde el principio, pero puede presentarlos más adelante ya que su aplicación se volverá más compleja. La excepción es TDD / BDD donde debería comenzar a usar al menos las pruebas de extremo a extremo y de unidad antes incluso de escribir una sola línea de otro código.

Entonces estás haciendo la pregunta incorrecta. La pregunta no es ¿qué es más simple? La pregunta es: ¿qué te ayudará al final y qué complejidad se adapta a tu aplicación? Si desea tener una lógica de aplicación y lógica comercial probada de forma sencilla, debe ajustar el código EF a otras clases que pueden ser objeto de burla. Pero al mismo tiempo, debe introducir otro tipo de pruebas para asegurarse de que el código EF funcione.

No puedo decir qué enfoque se ajustará a su entorno / proyecto / equipo / etc. Pero puedo explicar el ejemplo de mi proyecto anterior:

Trabajé en el proyecto durante aproximadamente 5-6 meses con dos colegas. El proyecto se basó en ASP.NET MVC 2 + jQuery + EFv4 y se desarrolló de forma incremental e iterativa. Tenía mucha lógica de negocios complicada y muchas consultas de bases de datos complicadas. Comenzamos con repositorys generics y alta cobertura de código con pruebas unitarias + pruebas de integración para validar el mapeo (pruebas simples para insertar, eliminar, actualizar y seleccionar entidad). Después de algunos meses, descubrimos que nuestro enfoque no funciona. Tuvimos más de 1.200 pruebas unitarias, cobertura de código de aproximadamente 60% (que no es muy buena) y muchos problemas de regresión. Cambiar cualquier cosa en el modelo EF podría presentar problemas inesperados en partes que no se tocaron durante varias semanas. Descubrimos que nos faltan pruebas de integración o pruebas de extremo a extremo para nuestra lógica de aplicación. La misma conclusión se tomó sobre un equipo paralelo que trabajó en otro proyecto y el uso de pruebas de integración se consideró como recomendación para nuevos proyectos.

¿El uso del patrón de repository agrega complejidad? En su escenario, no lo creo. Hace que TDD sea más fácil y tu código más manejable. Intente utilizar un patrón de repository genérico para obtener más separación y un código más limpio.

Si desea obtener más información sobre TDD y patrones de diseño en Entity Framework, consulte: http://msdn.microsoft.com/en-us/ff714955.aspx

Sin embargo, parece que estás buscando un enfoque para simular el Entity Framework de prueba. Una solución sería usar un método de semilla virtual para generar datos sobre la inicialización de la base de datos. Eche un vistazo a la sección de semillas en: http://blogs.msdn.com/b/adonet/archive/2010/09/02/ef-feature-ctp4-dbcontext-and-databases.aspx

También puedes usar algunos marcos burlones. Los más famosos que conozco son:

  • Rhino Mocks
  • Moq
  • Typemock (Comercial)

Para ver una lista más completa de frameworks burlándose de .NET, visita: https://stackoverflow.com/questions/37359/what-c-mocking-framework-to-use

Otro enfoque sería utilizar un proveedor de base de datos en memoria como SQLite . Estudie más en ¿Hay un proveedor en memoria para Entity Framework?

Finalmente, aquí hay algunos buenos enlaces sobre pruebas de unidades Entity Framework (Algunos enlaces se refieren a Entity Framework 4.0. Pero obtendrá la idea):

http://social.msdn.microsoft.com/Forums/en/adodotnetentityframework/thread/678b5871-bec5-4640-a024-71bd4d5c77ff

http://mosesofegypt.net/post/Introducing-Entity-Framework-Unit-Testing-with-TypeMock-Isolator.aspx

¿Cuál es el camino a seguir para falsificar mi capa de base de datos en una prueba unitaria?

Lo que hago es utilizar un simple objeto ISession y EFSession, que son fáciles de simular en mi controlador, de fácil acceso con Linq y fuertemente tipado. Inyectar con DI usando Ninject.

 public interface ISession : IDisposable { void CommitChanges(); void Delete(Expression> expression) where T : class, new(); void Delete(T item) where T : class, new(); void DeleteAll() where T : class, new(); T Single(Expression> expression) where T : class, new(); IQueryable All() where T : class, new(); void Add(T item) where T : class, new(); void Add(IEnumerable items) where T : class, new(); void Update(T item) where T : class, new(); } public class EFSession : ISession { DbContext _context; public EFSession(DbContext context) { _context = context; } public void CommitChanges() { _context.SaveChanges(); } public void Delete(System.Linq.Expressions.Expression> expression) where T : class, new() { var query = All().Where(expression); foreach (var item in query) { Delete(item); } } public void Delete(T item) where T : class, new() { _context.Set().Remove(item); } public void DeleteAll() where T : class, new() { var query = All(); foreach (var item in query) { Delete(item); } } public void Dispose() { _context.Dispose(); } public T Single(System.Linq.Expressions.Expression> expression) where T : class, new() { return All().FirstOrDefault(expression); } public IQueryable All() where T : class, new() { return _context.Set().AsQueryable(); } public void Add(T item) where T : class, new() { _context.Set().Add(item); } public void Add(IEnumerable items) where T : class, new() { foreach (var item in items) { Add(item); } } ///  /// Do not use this since we use EF4, just call CommitChanges() it does not do anything ///  ///  ///  public void Update(T item) where T : class, new() { //nothing needed here } 

Si quiero pasar de EF4 a Digamos MongoDB, solo tengo que hacer una MongoSession que implemente ISession …

Estaba teniendo el mismo problema al decidir sobre el diseño general de mi aplicación MVC. Este proyecto CodePlex de Shiju Varghese fue de gran ayuda. Se realiza en ASP.net MVC3, EF CodeFirst y también utiliza una capa de servicio y una capa de repository. La dependency injection se realiza usando Unity. Es simple y muy fácil de seguir. También está respaldado por 4 publicaciones de blog muy bonitas. Vale la pena echarle un vistazo. Y, no te rindas en el repository … pero sí.