Los test de integración y el contenedor de IoC

A diferencia de los test unitarios, en los que testeamos una unidad definida y de manera aislada, los test de integración están pensados para probar la interacción, no solo de distintas unidades, sino que además interactúan con dependencias externas reales como la base de datos.

Cuando escribo este tipo de pruebas me gusta tener un entorno los más similar a la realidad que se tiene en producción, es decir, si manejo soluciones con bases de datos relacionales y no relacionales, no trataré de crear ningún stub para emular el comportamiento de alguno de sus contextos.

Pero entonces, si queremos testear varios componentes del sistema y no debemos crear mocks o stubs para las dependencias, ¿qué usamos para resolverlas? pues, como lo hacemos en el entorno de producción! con el contenedor de IoC.

NOTA: No soy un gran amante de los contenedores de IoC, por distintas razones que darían para otro articulo, pero aquí expongo un caso especifico que si hace uso de un IoC container

No siempre usar la misma configuración del IoC para los Test

Aunque suene rara la contradicción (mantener todo lo más fiel al entorno de producción), en ocasiones es necesario re-escribir la configuración del contenedor de IoC, ¿para qué? para no tener que realizar peticiones a servicios que tienen algún tipo de throttling, por ejemplo, o por algún otro motivo que esconda alguna implementación concreta.

Un ejemplo concreto, SimpleInjector, VSTest y Entity Framework

En un proyecto en el que colaboro empleamos el SimpleInjector como contenedor de IoC, la parte de tests la realizamos con la suite de VSTest y como ORM tenemos Entity Framework.

La parte que hace referencia a la configuración del contenedor de dependencias es muy sencilla, simplemente registramos las dependencias como las necesitamos, tratando de ser lo más fieles a lo que tenemos en producción. Algo de la forma:

    public class IntegrationTestIocConfiguration : BaseIocConfiguration
    {
        public override void InitLifetimeDependantTypes(Container container)
        { 
            //AzureItems	  	
            //...
            //...

            //Infrastructure.Data Base
            //...
            //...

            //Some Repositories
            //...
            //...		
        }
    }

Ahora bien, en un entorno web de asp.net el punto de entrada lo localizamos en el Application_Start del Gloal.asax y es aquí donde se inicializan estos contenedores de dependencias. Esto es importante! porqué el contenedor solo se inicializa una vez (en dicho punto de entrada) y, si queremos mantener los test lo más similar al entorno de producción, dicho contenedor deberá ser inicializado una única vez para todas las pruebas que se ejecuten.

Pero en un proyecto de test, que no tiene un método main 😛 ¿donde ubicamos el punto de entrada? En este caso concreto, en el espacio de nombres Microsoft.VisualStudio.TestTools.UnitTesting tenemos el atributo AssemblyInitializeAttribute que nos servirá para realizar la inicialización, algo de la forma:

    [TestClass]
    public class IntegrationTestEntryPoint
    {
        public static Container IocContainer = null;

        [AssemblyInitialize]
        public static void Sartup(TestContext context)
        {
            Database.SetInitializer(new DropCreateDatabaseAlways<YourContext>());
            IocContainer = new Container();
            var configuration = new IntegrationTestIocConfiguration();
            configuration.Initialize(IocContainer);
            IocContainer.Verify();
        }
    }

Puede darse el caso que queramos que dichas pruebas usen una base de datos diferente, se conecten a servicios diferentes, o incluso que el Entity Framework use un seed diferente para los datos de estas pruebas, etc. Si lo has tenido en cuenta, y son parámetros configurables, puedes valerte del app.config para cambiar dichas direcciones.

Ahora, podemos empezar a escribir nuestros test, algo de la forma:

        [TestMethod]
        public void Should_Save_YourCaseToTest()
        {
            var service = IntegrationTestEntryPoint.IocContainer.GetInstance<IYourService>();
            var yourObj = new YourOject
                           {
                               //blah, blah, blah
                           };
            service.YourMethodSaveSuperTransacation(yourObj);
            var ctx = IntegrationTestEntryPoint.IocContainer.GetInstance<IAnyContext>();
            var obj = ctx.Get<YourObject>().FirstOrDefault(x => x.Property == "Value");
            Assert.IsNotNull(cmp);
            Assert.AreEqual(obj.Status, ObjectsStatus.Created);
            //Other interesting Asserts here
        }

Conclusión

No esta mal (en mi muy humilde opinión) usar el contenedor de dependencias para realizar los test de integración.

Resulta útil mantener estas pruebas lo más fieles posible al entorno de producción.

Pueden presentarse casos en los que simplemente es necesario sobrescribir las configuraciones del contenedor (como siempre, de todo depende!)

 

Cualquier feedback es bienvenido, no duden en comentar.

Hasta el próximo post.

Anuncios
Los test de integración y el contenedor de IoC

2 comentarios en “Los test de integración y el contenedor de IoC

  1. Me parece apropiada la primera parte de tu código en la cual, resuelves la instancia de tu servicio por medio del IoC container, aunque yo preferiría resolverla a partir de la misma clase de implementación YourService y no la interfaz IYourService, pues desde el principio ya sabes que vas a probar la integración de esa implementación específica y no otra de IYourService, así como conoces la estructura de base de datos “real” para los cuales verificarás que se integran bien.

    Hasta ahí tiene sentido usar el IoC container porque comprobará que resuelve las dependencias internas que te permitirán llegar desde tu servicio a una base de datos real. Pero ya luego cuando vas a verificar que el registro fue efectivamente insertado en la base de datos, no le veo utilidad a seguir usando el IoC, eso no te agrega nada más que una capa innecesaria.

    Me parece que lo ideal es que vayas lo más directamente para hacer el query en la base de datos, incluso podrías usar digamos que un micro-ORM que no empleas en tu código de producción. Algunos ven conveniente usar, para verificar que tu objeto quedó persistido en la base de datos, otra parte de tu código de producción, esta vez para consulta, digamos que algo como service.YourMethodGet(yourObj.Id), mientras que otros incluso en un solo test comprueban todas las operaciones de persistencia o digamos que el CRUD completo, lo que implica hacer varios asserts, lo cual no es tan malo en este contexto de pruebas de integración en el cual me parece que no aplica tanto eso de probar una sola cosa o se podría entender que la “una sola cosa” podría entenderse como la persistencia de una entidad o servicio.

    1. Hola Jorge! gracias por tu comentario… si, aun ando cocinando mi receta en esto, me ha parecido de mucha utilidad (aunque no lo veo muy “bonito” por eso de la unidad y blah blah) testear un todo (crud) con todos los asserts que implica, así de paso toqueteo con las consultas y comandos dentro de mi sistema. Eso que dices de usar otra herramienta para chequear la persistencia suena interesante! no se me había ocurrido, aunque me sigue gustando más lo de testear varios puntos ahí mismo.

      Saludos y gracias.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s