52

My previous question me hizo pensar de nuevo sobre capas, repositorio, inyección de dependencia y cosas de arquitectura como esta.ASP.NET MVC3 y Entity Framework Code first architecture

Mi arquitectura ahora se ve así:
Estoy usando el código EF primero, así que acabo de hacer las clases POCO, y el contexto. Eso crea db y modelo.
Nivel superior son las clases de nivel empresarial (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 arquitectura inicial incluía el repositorio, 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 arquitectura SIMPLE y comprobable para crear y la aplicación ASP.NET MVC3 con Entity Framework?

+0

Dijiste repositorios añaden 'complejidad' a su aplicación, pero yo diría que son una 'carga' inicial que facilita las pruebas. Burlarse de algunos repositorios es más fácil que burlarse de un contexto de datos completo. – Omar

+0

Sí, pero no quiero esa sobrecarga inicial en mi caso actual. Quiero progresar con la aplicación rápidamente. Ya perdí demasiado tiempo sin ningún progreso real. Agregar repositorios trae cosas como IoC, DI, etc. y tendré que escribir un montón de pruebas antes de llegar a la primera Vista. Sé que esta podría ser la solución correcta, pero no estoy buscando "la correcta". Estoy buscando una solución simple (aunque todavía comprobable). – Damb

Respuesta

94

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 repositorio 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 repositorio genérico:

public interface IGenericRepository<TEntity> 
{ 
    IQueryable<TEntity> GetQuery(); 
    ... 
} 

Y permite escribir algún método de negocio:

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

Ahora bien, si te burlas del repositorio que va a utilizar LINQ-to- Objetos 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 repositorio. 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 las pruebas unitarias contra los simulacros.

Por lo tanto, 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 WatiN y posiblemente también SpecFlow para BDD, pero esto realmente agregará mucho trabajo pero tendrá su aplicación realmente probada. Si desea leer más sobre TDD, recomiendo this book (la única desventaja es que los ejemplos están en Java).

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

Ejemplo:

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

Ahora usted puede simplemente escribir pruebas de integración para probar la implementación del este repositorio porque las consultas se esconden en esta clase y no expuestos a las capas superiores. Pero este tipo de repositorio se considera de alguna manera como una implementación antigua utilizada con procedimientos almacenados. Perderá muchas funciones de ORM con esta implementación o tendrá que hacer un montón de trabajo adicional; por ejemplo, agregue specification pattern para poder definir consultas 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 del controlador.

Editar basado en comentario:

No digo que necesita 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.

Los proyectos pequeños y sencillos se pueden crear sin pruebas (bueno, 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 presenta nuevas características o mantener el proyecto es muy difícil porque nunca está seguro de 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 de unidades 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 de la historia/función del caso de uso/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 de extremo a extremo, no necesita escribir la 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.

Así que usted está 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 se puede decir qué enfoque se ajuste a su entorno/proyecto/equipo/etc, pero me puede explicar ejemplo de mi pasado proyecto:

que trabajaron en el proyecto durante unos 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 repositorios genéricos 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.

+0

Hmm. Así que si entendí correctamente, estás diciendo que usar mocks es para pruebas unitarias de la lógica de negocio y necesito hacer pruebas de integración con contexto de ef real, así como pruebas de extremo a extremo (entiendo esto como pruebas funcionales/de usuario. .con herramientas li ke Watin). Pero no entiendo el punto acerca de la arquitectura. Me alegra que me haya dado pistas sobre cuáles son los problemas, pero no tengo experiencia en este campo, así que no sé cuál es la mejor solución. Y eso es lo que estoy buscando aquí. Y estoy hablando de mejor en un significado "más fácil" o "simple". – Damb

+0

Gracias. Agradezco mucho sus respuestas y explicaciones. Creo que estoy usando el "código wrap ef para otra clase" (mis clases de Proveedor) ahora mismo. Y solo para agregar algo de contexto a mis preguntas: estoy creando una aplicación simple basada en la administración de tareas (en el contexto del proyecto) para los usuarios (+ sistema experto, que no cambia la arquitectura de todos modos porque solo consume datos y proporciona resultados simples). Es mi propio proyecto (nadie más está trabajando en él) y no creo que tenga un gran futuro. – Damb

+1

@Ladislav: ¿Es posible que las herramientas de análisis de código estático puedan detectar problemas como el que describió (métodos Linq no soportados para Linq a Entidades)? Si es así, entonces podría eliminar una clase de errores sin tener que escribir pruebas de unidades para ellos, y obtener más confianza de que sus burlas en las pruebas que escribe "realmente funcionarán". Probablemente no podría resolver problemas de integridad referencial, pero como dijiste, se pueden solucionar con pruebas de integración (en lugar de E2E). –

13

¿El uso del patrón de repositorio 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 repositorio genérico para obtener más separación y un código más limpio.

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

Sin embargo parece que usted está buscando un acercamiento a Entity Framework prueba falsa. 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 Seed sección en: http://blogs.msdn.com/b/adonet/archive/2010/09/02/ef-feature-ctp4-dbcontext-and-databases.aspx

También puede utilizar algunos marcos de burla. Los más famosos que conozco son:

Para ver una lista más completa de los marcos .NET burlarse, echa un vistazo a: https://stackoverflow.com/questions/37359/what-c-mocking-framework-to-use

Otro enfoque sería usar un proveedor de base de datos en memoria como SQLite. Estudie más en Is there an in-memory provider for Entity Framework?

Finalmente, aquí hay algunos buenos enlaces sobre la evaluación 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

What is the way to go to fake my database layer in a unit test?

+0

Gracias por su aporte, algunos enlaces interesantes están ahí. Pero mi pregunta en realidad no es tanto sobre pruebas y burlas. Se trata más bien de buscar una arquitectura sencilla, rápida y fácil sin gastos generales. Algo que puede usar para probar y desarrollar aplicaciones de forma rápida y fácil sin necesidad de preparar líneas de códigos XYZ, si su método realmente devuelve un valor de cadena. {Sry por un poco de sarcasmo.} – Damb

+0

@dampe: Bueno, en lugar de escribir interfaces e ir con datos de burla manual, sugerí algunas soluciones adicionales que pueden hacer mucho por ti. Una vez más, me gustaría ir con un patrón de repositorio genérico en estos casos y nunca sentí que agrega complejidad a mi solución. Espero eso ayude. – Kamyar

+0

En cuanto a la sugerencia acerca de un repositorio genérico, consulte este tutorial: http://www.asp.net/entity-framework/tutorials/implementing-the-repository-and-unit-of-work-patterns-in-an-asp -net-mvc-application – tdykstra

1

que estaba teniendo el mismo problema de decidir sobre el diseño general de mi aplicación MVC. This Proyecto CodePlex de Shiju Varghese fue de mucha ayuda. Se realiza en ASP.net MVC3, EF CodeFirst y también utiliza una capa de servicio y una capa de repositorio. La inyección de dependencia 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 repositorio ... pero sí.

+2

Gracias, eché un vistazo a ese código de solución y casi no importa lo que no quiero ... todos esos repositorios, IoC, fábricas, etc. Esto no es lo que imagino cuando alguien dice " arquitectura simple ":) – Damb

+0

El diseño más simple que puedo sugerir (aunque no lo recomiendo) es crear los objetos de Contexto EF directamente desde sus controladores, pero como se indicó en su pregunta ya lo intentó y ya tiene problemas con él. – Ben

2

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<T>(Expression<Func<T, bool>> expression) where T : class, new(); 
     void Delete<T>(T item) where T : class, new(); 
     void DeleteAll<T>() where T : class, new(); 
     T Single<T>(Expression<Func<T, bool>> expression) where T : class, new(); 
     IQueryable<T> All<T>() where T : class, new(); 
     void Add<T>(T item) where T : class, new(); 
     void Add<T>(IEnumerable<T> items) where T : class, new(); 
     void Update<T>(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<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class, new() 
     { 

      var query = All<T>().Where(expression); 
      foreach (var item in query) 
      { 
       Delete(item); 
      } 
     } 

     public void Delete<T>(T item) where T : class, new() 
     { 
      _context.Set<T>().Remove(item); 
     } 

     public void DeleteAll<T>() where T : class, new() 
     { 
      var query = All<T>(); 
      foreach (var item in query) 
      { 
       Delete(item); 
      } 
     } 

     public void Dispose() 
     { 
      _context.Dispose(); 
     } 

     public T Single<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class, new() 
     { 
      return All<T>().FirstOrDefault(expression); 
     } 

     public IQueryable<T> All<T>() where T : class, new() 
     { 
      return _context.Set<T>().AsQueryable<T>(); 
     } 

     public void Add<T>(T item) where T : class, new() 
     { 
      _context.Set<T>().Add(item); 
     } 

     public void Add<T>(IEnumerable<T> items) where T : class, new() 
     { 
      foreach (var item in items) 
      { 
       Add(item); 
      } 
     } 

     /// <summary> 
     /// Do not use this since we use EF4, just call CommitChanges() it does not do anything 
     /// </summary> 
     /// <typeparam name="T"></typeparam> 
     /// <param name="item"></param> 
     public void Update<T>(T item) where T : class, new() 
     { 
      //nothing needed here 
     } 

Si quiero cambiar de EF4 a digamos MongoDB, sólo tengo que hacer una MongoSession que implementan ISession ...

+0

Gracias. Creo que estoy haciendo algo similar ... excepto esa parte genérica y Ninject :) – Damb

Cuestiones relacionadas