El repository en sí no suele ser probado?

Lo siento, pero soy nuevo en los patrones de repositorys, pruebas unitarias y herramientas de orm.

He estado investigando sobre pruebas unitarias y el patrón de repository, y llegué a algunas conclusiones, me pregunto si estoy en lo cierto.

El patrón de repository facilita que las pruebas unitarias sean reemplazadas en el controlador que hace uso de él, por ejemplo, ¿verdad? Porque crear un stub / falso contexto (en EF) o sesión (en NH) es más difícil, ¿no? El repository en sí no está probado? ¿Por qué?

Usando EntityFramework o NHibernate con un patrón de repository, si quiero probar mis repositorys, ¿necesito hacer pruebas de integración? Porque si uso una implementación falsa de mi contexto / sesión, ¿no estoy haciendo pruebas reales? Porque el contexto / sesión en sí es el repository (me refiero a que implementan la lógica real de Agregar, Eliminar, Editar, GetById, GetAll, …)?

El patrón de repository con EF o NH es como un envoltorio? (No solo un envoltorio, sé que este es un concepto de importación del dominio).

Diferiría estrictamente entre EF y NH en este caso y no incluiría ambas tecnologías en la misma pregunta. Simple NH es más maduro y tiene una architecture que lleva al código que puede ser probado más fácilmente. También en el caso de NH puede simplemente cambiar la base de datos a otra (como SQLite) y seguirá funcionando igual, lo que no tiene que ser cierto en el caso de EF, donde cambiar la base de datos puede dar lugar a pruebas de aplicaciones completamente diferentes, especialmente si cambia de base de datos MS y no MS.

¿Cuál es el repository? Veamos la definición de Martin Fowler :

Un repository media entre el dominio y las capas de mapeo de datos, actuando como una colección de objetos de dominio en memoria. Los objetos del cliente construyen especificaciones de consulta de forma declarativa y las envían al repository para su satisfacción. Los objetos se pueden agregar y eliminar del Repositorio, como pueden hacerlo desde una simple colección de objetos, y el código de mapeo encapsulado por el Repositorio llevará a cabo las operaciones apropiadas detrás de las escenas. Conceptualmente, un Repositorio encapsula el conjunto de objetos persistidos en un almacén de datos y las operaciones realizadas sobre ellos, proporcionando una vista más orientada a objetos de la capa de persistencia. El repository también es compatible con el objective de lograr una separación limpia y una dependencia unidireccional entre las capas de mapeo de datos y dominio.

Buena definición. Ahora piensa en el propósito de DbSet :

  • ¿Actúa como en la recolección de memoria? Sí, puede usarlo para obtener entidades de la base de datos o usar propiedades Local para obtener entidades ya cargadas.
  • ¿Puede el cliente consultar especificaciones de manera declarativa? Sí, se llama linq-to-entities.
  • ¿Se pueden agregar o eliminar objetos a partir de la colección? Sí puede.
  • ¿El mapeo está encapsulado? Sí lo es.
  • ¿Hay una separación limpia? En términos de lógica, sí. En términos de API no, porque exponer IDbSet al modelo de dominio hará que el modelo de dominio dependa de la tecnología: EF. ¿Es un problema? En teoría sí, para el sí purista, pero en el 99% realmente no es un problema porque la situación en la que necesitas cambiar la API es rara y siempre implica grandes cambios incluso si separas correctamente las API.

DbSet es un repository. La única diferencia entre usar DbSet directamente y envolverlo en algún repository genérico es una separación. Esto me lleva a mi respuesta anterior a una pregunta similar: Repositorio genérico con EF 4.1 ¿Cuál es el punto?

Ahora, ¿cuál es el propósito del repository en su aplicación? Vi sus preguntas anteriores, incluida esta, donde tiene su BaseRepository construido sobre el marco de Entity. Si realmente quiere decir esto como un repository base que será el padre de sus repositorys especializados trabajando en raíces agregadas para su modelo de dominio y exponiendo métodos especializados relacionados solo con el tipo de entidad expuesta específica, entonces sí – está usando un patrón de repository y lo necesita. Pero si solo está envolviendo el contexto y el conjunto único y llama a este repository, lo más probable es que haya creado solo una capa redundante con un valor añadido dudoso porque eso es solo una envoltura sobre DbSet .

Solo hay un escenario donde su repository (contenedor DbSet ) tendrá sentido en tal caso:

  • El contenedor nunca expondrá IQueryable (linq-to-entities)
  • El contenedor nunca aceptará Expression<> y lo pasará internamente a IQueryalbe (linq-to-entities)

Este es el único escenario que le ofrecerá depósitos totalmente divisibles => su capa superior se puede probar fácilmente en una unidad. No va a repositorys de pruebas unitarias y no se va a burlar del contexto utilizado en los repositorys. Los repositorys envuelven el acceso a los datos y la lógica de mapeo; las únicas pruebas razonables en el caso de los repositorys son las pruebas de integración. ¿Cuál es el problema de este escenario? Perderá todo el poder de LINQ y tendrá que ajustar / volver a implementar algunos métodos y tipos implementados en EF. Este tipo de repositorys es el mismo que se usa cuando se envuelve el acceso a datos usando procedimientos almacenados.

Si no sigue ese escenario, su vida será mucho más fácil. Tendrá consultas definidas por LINQ, pero no podrá probar el código de la unidad porque no hay falsa / falsa que aún evalúe las consultas como linq-to-entities. Una vez que se burla de DbSet o DbSet , usará linq-to-object, que es superconjunto de linq-to-entities. Puede escribir fácilmente una consulta que pasará una prueba en DbSet burlado pero fallará en tiempo de ejecución con un DbSet real. Aquí hay más sobre este problema y aquí está el ejemplo de consulta que pasará la prueba pero fallará en tiempo de ejecución. En este caso, debe usar pruebas de integración (con base de datos real) para todos los métodos que utilicen consultas de linq-a-entidades en la parte superior de sus repositorys.

La interfaz de repository pertenece a la capa de dominio. Pero la implementación pertenece a la infraestructura o capa de acceso a datos. Esta implementación generalmente no se prueba con pruebas unitarias . Utiliza la API de ORM en gran medida, por lo que es extremadamente difícil y un desperdicio probar el repository de forma aislada. Este problema no es específico de los repositorys: no se burle de los tipos que no le pertenecen.

El repository debe probarse con pruebas de integración , utilizando ORM real. En la base de datos de memoria es un enfoque muy popular para este problema.

… Porque si uso una implementación falsa de mi contexto / sesión, ¿no estoy haciendo pruebas reales?

Incluso si logras hacerlo (lo cual realmente dudo en el caso de NHibernate) estarás perdiendo el tiempo. La interfaz Session / Context está fuera de tu control y tu prueba simplemente reiterará tu suposición sobre cómo funcionará realmente.

Porque el contexto / sesión en sí es el repository?

No. El contexto / sesión es la implementación del patrón UnitOfWork . No es parte de tu dominio. Esto es infraestructura

El patrón de repository con EF o NH es como un envoltorio solamente?

El repository es un concepto de dominio importante, no es solo un ‘envoltorio’. Al igual que su aplicación no es una ‘envoltura’ sobre la base de datos. Creo que la definición de la interfaz del repository DDD debe basarse en el lenguaje ubicuo tanto como sea posible y no debe incluir palabras ni tipos relacionados con ORM.

Es bastante común crear repositorys que sean solo envoltorios livianos de su acceso de base de datos y poner los métodos comerciales en una capa de servicio de entidad que depende de los repositorys, sí. Por lo tanto, puede probar los servicios de las entidades mediante el uso de repositorys que envuelven, por ejemplo, un DB en memoria.

Cuando pruebas unitarias, debes tener claro qué “unidad” estás probando. Te burlas de cosas que están fuera de tu Unidad mientras pruebas tu Unidad. Las cosas que se burlan pueden probarse por separado.

Hay otro tipo de prueba en la que prueba piezas más grandes. Entonces puede probar su código + repository + base de datos. Hay un valor en eso, pero no está probando las mismas cosas. En particular, cuando se utiliza una base de datos real, es más difícil forzar su código hacia abajo en algunas rutas de error.

¿Deberías probar {tu código + repository} y burlar la base de datos? Depende de cuán complejo y cuán probado esté el repository en sí mismo.