EF 4.0: Testeando nuestros repositorios

De todos es sabido mi cariño por esta tecnología, reflejado sin duda en los numerosos artículos, entradas de blog y por su puesto el Libro ADO.NET EF publicado sobre la misma junto a mis colegas Octavio Hernandez y Eduardo Quintás. Por supuesto, ya desde hace bastante tiempo han salido betas y CTP sobre features que tendremos disponibles en Visual Studio 10 para lo que se ha llamado “Entity Framework 4.0”. A lo largo de futuros post y por supuesto de la segunda versión del libro intentaremos desgranar lo bueno de esta nueva versión y todos los ‘inconvenientes’ que hace más sencillos para la gente que se ha enfrentado a esta tecnología, sobre todo, para aquellos que la han implementado ( o intentado implementar ) en aplicaciones N-CAPAS.

Para esta primera entrada, de verdad :-), sobre EF 4.0, me gustaría centrarme en aspectos de test y test-doubles, es decir, ver como podemos con las nuevas API’s tratar  de testear nuestros repositorios de datos, y como hacer test-doubles de los mismos sin necesidad de acudir realmente a la base de datos.

El primer cambio importante que uno ve cuando de entrada crea un proyecto con EDM en Visual Studio 2010 y hecha un vistazo al código, independientemente de la plantilla de T4 seleccionada ( Clases prescriptivas, POCO o Selft Tracking Entities ) es que nuestros objetos de consulta que el contenedor pone a nuestra disposición ya no son directametne ObjectQuery<T> sinó ObjectSet<T>. Esta nueva clase, no es más que una herencia de la anterior, la cual, además de los tradicionales métodos de construcción de consultas agrega una serie de elementos que nos simplificarán un poco las cosas, como son por ejemplo los métodos AddObject, Attach y DeleteObject. Aunque a priori pueda parecer una nimiedad fíjese como en la versión actual de EF cuando queremos ‘Atachar’ una entidad dentro del contexto debemos realizar una especificación del EntitySet por medio de una cadena de caracteres, algo no muy elegante a la par de una fuente propensa de errores cuando el proyecto evoluciona, es decir, cuando los modelos tienen una frecuencia de cambio relativamente alta. Le recomiendo la lectura del post de Alex James dónde expone estos simples cambios y de paso como podríamos simularlos en .NET 3.5 SP1.

Además de los métodos comentados, la parte más interesante de esta nueva clase es que la misma se basa en la implementación de una interfaz, IObjectSet<T>, y por lo tanto es susceptible de ser simulada con cierta rapidez. Precisamente, es este punto, el que nos interesa dentro del trabajo a realizar para la creación de test-doubles con los contenedores de EF.

Supongamos que creamos un nuevo modelo de entidades que disponga de una entidad Person y que sobre el mismo decidimos crear nuestra implementación de contedor y entidades POCO.

Un ejemplo de nuestro contenedor podría ser algo similar a lo siguiente:

 

public class ModelContainer
        :ObjectContext
{
        #region Constructor

        public ModelContainer()
            :base("name=ConnectionStringName","ContainerName")
        {
        }

        #endregion

        #region ObjectSets

        private ObjectSet<Person> mPerson = null;
        public ObjectSet<Person> Person
        {
            get
            {
                return mPerson ?? this.CreateObjectSet<Person>();
            }
        }

        #endregion
}

Como podrá observar sería muy sencillo readaptar nuestro contenedor para que el mismo se basara en una interfaz, que por ejemplo llamaremos IContainer y que tendría la siguiente firma.

public interface IContainer
{    
    IObjectSet<Person> Person { get; }
}

 

public class ModelContainer
        :ObjectContext,IContainer
{
        #region Constructor

        public ModelContainer()
            :base("name=ConnectionStringName","ContainerName")
        {
        }

        #endregion

        #region ObjectSets

        private ObjectSet<Person> mPerson = null;
        public IObjectSet<Person> Person
        {
            get
            {
                return mPerson ?? this.CreateObjectSet<Person>();
            }
        }

        #endregion
}

 

Llegados a este punto, podríamos decir que ya tenemos las bases para poder hacer simulaciones de un contendor de trabajo. La pieza que tendríamos que simular sería una implementación ‘dummy’ de la interfaz IContainer, la cual puede ser fácilmente construída con Stub’s del que ya hemos visto cosillas en un video de Channel9@Spain o bien con cualquier otro framework de Mockering-Stubing como NMock, RhinoMock etc.. El problema principal que nos queda por resolver es la creación de nuestras simulaciones de IObjectSet, puesto que además de los elementos vistos esta interfaz es IQueryable<T> y por lo tanto debermos de dar una implementación a los métodos de la misma. Para resolver esta problemática utilizaremos el método extensor, AsQueryable(), que todos los elementos IEnumerable<T> ,como las listas genéricas, poseen y mediante el cual, podemos transformar directamente una colección de este tipo a Queryable<T>.

Con el fin de hacer esto de una forma más genérica y reutilizable para todos nuestros ObjectSet  partiremos de la siguiente clase.

 

class MockObjectSet<T>
        :IObjectSet<T>
        where T:class

    {
        #region Members

        List<T> mInnerList = null;

        #endregion

        #region Constructor

        public MockObjectSet(List<T> innerList)
        {
            //Set InnerList
            mInnerList = innerList;
        }

        #endregion

        #region IObjectSet<T> Members

        public void AddObject(T entity)
        {
            if (mInnerList != null)
                mInnerList.Add(entity);
        }

        public void Attach(T entity)
        {
            //TODO: For future post 🙂
        }

        public void DeleteObject(T entity)
        {
            if ( mInnerList != null )
                mInnerList.Remove(entity);
        }

        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            return mInnerList.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region IQueryable Members

        public Type ElementType
        {
            get { return typeof(T); }
        }

        public System.Linq.Expressions.Expression Expression
        {
            get { return mInnerList.AsQueryable().Expression; }

        }
        public IQueryProvider Provider
        {
            get { return mInnerList.AsQueryable().Provider; }

        }
                

        #endregion
    }

 

Cómo se puede observar, esta nueva clase nos es más que la implementación de la interfaz IObjectSet a partir de una lista de elementos cualquiera y puede ser construída fácilmente por medio, por ejemplo, de un método extensor aplicado a las listas genéricas.

static class Extensions
{
    public static MockObjectSet<T> AsObjectSet<T>(this List<T> list)
            where T:class
    {
         return new MockObjectSet<T>(list);
    }
}

 

Llegados aquí, ya hemos construídas todas las bases de nuestro trabajo, vamos entonces, a realizar la tarea que nos ocupaba que consistía en realizar la implementación de Test de nuestros contenedores sin tener que acudir a la base de datos, para ello iremos utilizando todas las partes que hemos visto y creado anteriormente.

 

[TestMethod()]
public void TestPersonObjectSet()
{
            //Triple AAA ( Arrange, Act, Assert.. )

           /*
            * Arrange
            */

           //Create a dummy implementation of IContainer
           SIContainer containerDouble = new SIContainer();

           //Create a list of dummy values for Person ObjectSet
           List<Person> persons = new List<Person>()
           {
               new Person(){IdPerson=1,FistName="Unai",LastName="Zorrilla"},
               new Person(){ IdPerson=2,FistName="Pablo",LastName="Alvarez Doval"}
           };

           //Set Stub ObjectSet
           containerDouble.PersonGet = () => persons.AsObjectSet();

           /*
            * Act
            */

           Person personId1 = ((IContainer)containerDouble).Person.Single(p => p.IdPerson == 1);

           List<Person> collection = ( from c in ((IContainer)containerDouble).Person
                                       orderby c.IdPerson select c).ToList();

           /*
            * Assert
            */

           Assert.AreEqual(personId1.FistName, "Unai");
           Assert.AreEqual(personId1.LastName, "Zorrilla");
           Assert.IsTrue(persons.Count > 0);
}

 

 

Como podemos observar en el código anterior, nuestro ejemplo de test crea una instancia de un contenedor creado por Stub’s, al cual le asignamos como ObjectSet de Person el creado a partir de una lista con nuestro nuevo método extensor, posteriormente, haremos las consultas, de igual forma que si estuvieramos trabajando contra ObjectSet de nuestros modelos de EF, es decir, como si estuvieramos trabajando con bases de datos.

 

Por supuesto, esto no es más que un ejemplo de trabajo sencillo, en futuras entradas iremos viendo como poner en práctica todo esto contra repositorios de nuestros contenedores de trabajo y como modificar nuestras plantillas de T4 para que podamos tener un alto grado de productividad, tanto para la realización de aplicaciones con EF 4.0 como para que las mismas sean fácilmente testeables.

 

Espero que os guste…

Un saludo

Unai Zorrilla Castro