Testing y EF 4.1

Hace ya un tiempo que en este mismo blog hablé de como con EF 4.0 podíamos construir tests de nuestros repositorios. En esta ocasión, me gustaría repetir cierto contenido de esa misma entrada aplicándolo a EF 4.1 y ampliandolo con algún tip que nos permita facilitar no solamente los ‘mocks’ sino también las pruebas contra la base de datos y su posible repetición. Algunos de los elementos/patrones que mencionaremos en este post los daremos por sabidos, no obstante, seguro que puede encontrar en la web mucha y muy buena información acerca de los mismos que le serán de ayuda.

Empezaremos por un ejemplo sencillo, partiremos de una unidad de trabajo similar a la siguiente:

 

Parece claro, que si este componente es una dependencia de cualquier otro elemento, por ejemplo un repositorio, no vamos a poder testear estos de una forma aislada, por lo menos no de la forma más sencilla posible, y por lo tanto, sería bueno establecer, para esta unidad de trabajo un contrato que nos sirviera realmente como la abstracción de nuestra unidad de trabajo. Además, si este contrato podemos hacer que no se base en la implementación del queryable de EF, DbSet en EF 4.1, sería mucho mejor. Es decir, buscamos algo como lo siguiente:

Ahora, si vasamos nuestras dependencias en este contrato ya podríamos realizar nuestras pruebas simulando el comportamiento de esta unidad de trabajo. Al principio, puede parecer osado realizar una simulación de DbSet, sabiendo que este es un queryable y por lo tanto un  “interprete” de árboles de expresiones. Sin embargo, veremos como en muy pocas lineas podemos crear un elemento que nos permita representar un IDbSet en memoria.

 

Este elemento del que hablamos, nosotros le llamaremos MemorySet, no es más que la implementación de un IDbSet<T> basada en una lista genérica, la cabecera de esta clase sería algo similar a :

 

Al implementar IDbSet, estamos obligados a establecer el comportamiento para métodos como Add,Attach,Find,Remove etc… todos ellos perfectamente asignables al trabajo con una lista genérica. Quizás en lo que podamos tener más dudas es en la implementación de los elementos específicos de IQueryable, mostrados a continuación:

 

Esta implementación, la podemos realizar fácilmente gracias a un método extensor poco conocido como es AsQueryable,  método que nos ofrece cualquier elemento IEnumerable<T> gracias al cual podemos transformar cualquier enumerable a un elemento queryable.

Una vez terminada la creación de nuestro MemorySet, ya estamos entonces, en disposición de realizar nuestra simulación de nuestra unidad de trabajo en nuestros tests. Llegados hasta aquí seguramente alguno esté preguntando, bueno, bien, ¿pero que pasa con los test hacia la base de datos, los tenemos que hacer o solamente los simulamos?. La verdad es que esto es buena pregunta, porque mezcla diferentes aspectos, por un lado los teóricos que considerarían esto como un test de integración y por el otro lado el pragmatismo… ( hace ya bastante de esto Rodrigo Corral escribió un buen post ). Asumiendo que por un motivo u otro ( un buen motivo es que no todos los elementos de LINQ están soportados en EF y por lo tanto, consultas válidas sobre nuestras simulaciones no tienen porque traducirse de forma correcta) vamos a hacer pruebas con la base de datos, estas deberian asegurarnos ciertos elementos esenciales, como por ejemplo que las pruebas sean repetibles, es decir, que si ejecutamos dos veces seguidas una misma prueba el comportamiento no cambia. Esto, con bases de datos, puede ser más o menos dificil de conseguir, de hecho, hay diferentes patrones para ponerlo en práctica, muy bien explicados y documentados en XUnit Patterns. Como muchos sabréis, la llegada de EF 4.1, además de todo lo que tiene que ver con Code First incluye algún elemento importante que podría facilitarnos esta tarea. Elementos como la generación automática del esquema o los inicializadores nos permitirán de una forma más que sencilla hacer que nuestros test contra una base de datos sean simples de construir y repetibles (asumiendo que hacemos Database Sandbox).

Los inicializadores, no son más que elementos de tipo IDatabaseInitializer<TContext> gracias a los cuales podemos ejecutar un determinado código durante la inicialización de la base de datos con la que esté configurada para trabajar una unidad de trabajo. Por defecto, EF 4.1 nos proporciona tres inicializadores, DropCreateDatabaseAlways, CreateDatabaseIfNotExist,DropCreateDatabaseIfModelChanges. Los nombres seguro que le son completamente auto descriptivos. Estos inicializadores, se establecen dentro de la clase estática Database por medio de su método SetInitializer como puede verse en la siguiente linea:

Los tres inicializdores anteriores ofrecen un método virtual llamado Seed, gracias al cual, además del borrado [y generación] de la base de datos, podemos establecer un conjunto de datos de inicialización. En las siguientes lineas puede ver un ejemplo de una creación de un inicializador para la unidad de trabajo definida anteriormente, fíjese como no le costaría hacer que los mismos elementos con los que construye su mock fueran los elementos con los que inicializa la base de datos.

 

Ahora, una vez que ya sabemos como establecer un inicializador a las unidades de trabajo de EF, solamente nos queda ver como podemos hacer que este se establezca antes de ejecutar cualquiera de los tests que tenemos en un proyecto y, por lo tanto, genere y pueble la base de datos antes de pasar cualquier prueba. Esto, con MSTest es realmente sencillo, bastaría con crear un Assembly Initialize, es decir, un método que la infraestructura de MS TEST ejecutará cuando el ensablado de test sea inicializado ( antes de pasar cualquier prueba por lo tanto )

 

En NUnit, sino me equivoco AssemblyInitialize se corresponde con la definición de un SetupFixture fuera de cualquier namespace, así quiero recordar que era, lógicamente esto no lo puedo asegurar y además es probable que haya cambiado …

 

Espero que esta entrada os haya resultado de interes..

 

Saludos

Unai Zorrilla Castro

3 comentarios en “Testing y EF 4.1”

  1. Interesante la forma de implementar el Dbset en memoria, todo lo que he visto por internet lo hacían bastante más complicado…

    Sobre el contrato IBlogUnitOfWork al actualizar una entidad se debe marcar como “modified” yo añadí un método en el contrato para cambiar estados a la entidad

    void ChangeState(obj entity, state xx);

    Se te ocurre alguna forma más limpia?

    Un saludo

  2. Carlos, creo que es la forma más simple de implementar esta simulación. Con respecto a tu pregunta eso es implementacion y casi prefiero no establecer eso en el contrato, es muy de implementación… y hacerlo en el repositorio.

    Preguntoncojonero.. ya está!

    Unai

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *