Unit tests

Pruebas Unitarias y Entity Framework, dos cosas importantes en el desarrollo de nuestros días, pero que no tienen mucho en común. Las pruebas unitarias tienen por objetivo probar partes específicas de nuestra aplicación, un método en una clase, por ejemplo, de manera aislada, atómica e independiente. Es en este punto donde surge el problema con Entity Framework, dado que EF utiliza una conexión a base de datos para su funcionamiento y es algo de lo que una prueba unitaria no puede depender bajo ningún concepto.

¿Qué opciones tengo para hacer pruebas que dependen de Entity Framework?

Debido a que no podemos depender de una conexión a base de datos porque haría que nuestras pruebas no sean independientes, nuestra primera opción seria crear una interfaz entre el código que queremos probar y EF, de esa manera podemos reemplazar la implementación de EF en nuestras prueba (pero esta opción no es la más divertida :( ).
Solution architect
Esta solución está enfocada desde el punto de vista arquitectónico de la aplicación y es muy válida pero no es la única.

La solución alternativa: Effort.EF6

Así como podemos estructurar nuestra aplicación para poder reemplazar la dependencia con la base de datos, Entity Framework fue construido de tal forma que podemos reemplazar ciertas partes, lo que hace el paquete Effort.EF6 es reemplazar el proveedor ADO.NET, Effort ejecuta todas las operaciones en un ligera base de datos en memoria en vez de una base de datos tradicional haciendo realmente fácil crear data-driven tests que pueden ejecutarse sin la existencia de una base de datos externa.

Empezando con Effort.EF6

Veamos que tan fácil (o difícil) es usar Effort.EF6 (spoiler alert: es fácil).

  1. Empezaremos con un proyecto que tiene componentes que utilizan Entity Framework, si deseas puedes revisar el código que usare en esta demo en github.
    TestingEF

  2. Agregamos un proyecto de pruebas unitarias a la solución e instalamos el paquete de Effort con el siguiente comando:

     PM> Install-Package Effort.EF6 
    

Installing packages

Y les recuerdo que necesitaran agregar la referencia System.Data.
Referencing System.Data

  1. Ahora, para escribir nuestra primera prueba unitaria debemos resaltar un concepto sacado de la documentación, la conexión que creamos usando Effort puede ser Trasient o Persistent.

Una conexión Trasient, crea una base de datos en memoria mientras la conexión está abierta, y cuando se cierra o se llama al método Dispose esta se destruye.

Una conexión Persistent, crea una base de datos la primera vez que se accede al proveedor y se mantiene en memoria durante toda la ejecución de la aplicación a pesar de que se cierre la conexión y se vuelva a abrir repetidas veces.

  1. Lo primero que debemos hacer es un pequeño ajuste a nuestro contexto de Entity Framework para que soporte el uso de Effort, solo necesitamos agregar una sobrecarga que acepte una conexión.

     public TechiesDbContext(DbConnection connection)
            :base(connection,contextOwnsConnection:true)
     {
     }
    

Overload

  1. Ahora, para nuestra primera prueba, que no requiere datos de ejemplo (punto tedioso en unit testing de este tipo, aunque no con Effort ;)), crearemos la conexión de la siguiente forma:

     [TestClass]
     public class TechiesUnitTest
     {
        private TechiesDbContext _context;
    
        [TestInitialize]
        public void Initialize()
        {
         _context = new TechiesDbContext(Effort.DbConnectionFactory.CreateTransient());
        }
     }
    


Effort.DbConnectionFactory también posee un método CreatePersistent para crear una conexión persistente.

  1. Ahora podemos agregar nuestra prueba unitaria, que en este ejemplo, verifica que al consultar un registro inexistente mi clase lance una excepción.

     [TestMethod]
     [ExpectedException(typeof(InvalidOperationException))]
     public void GetById_throwException_whenIdDoesnExists()
     {
        //Arrange
        var techies = new Techies(_context);
        var fakeId = 5;
         
        //Act
        var a = techies.GetTechieById(fakeId);
    
        //Assert
        Assert.Fail("This test need to raise an exception.");
     }
    

Test result

¿Qué ofrece Effort.EF para los datos de ejemplo?

Effort posee el concepto de DataLoader, un dataloader es una clase que nos permite cargar datos de ejemplo, el origen más común son archivos .csv.

CsvDataLoader

Este loader nos permite elegir un folder conteniendo archivos .csv con nombres que encajan con los nombres de las tablas que van a popular.

Un punto interesante es que Effort incluye una herramienta que nos permite extraer los archivos .csv a partir de los datos de nuestra base de datos.

Poniendo a prueba el CsvDataLoader

  1. En primer lugar, generaremos los datos de ejemplo para esta demo a partir de la base de datos que tengo, es en realidad súper simple solo para probar el concepto, pero funciona muy bien con bases de datos más grandes y con muchas relaciones.
    SQL Server

  2. Para conseguir la herramienta, tenemos la opción de compilarlo y ejecutarlo directamente desde Visual Studio o descargarlo en el siguiente enlace.

  1. Ahora para generar los datos de ejemplo nos basta con especificar el proveedor de base de datos, la cadena de conexión y el folder donde se guardarán los archivos.

  2. Tomamos el archivo .csv y lo incluimos en el proyecto de pruebas unitarias en Visual Studio.

Debemos asegurarnos de que nuestros archivos .csv se copien a la carpeta output, usando la ventana de propiedades(alt+enter) podemos hacerlo.
Properties

  1. Ahora ajustamos el código de inicialización de nuestras pruebas unitarias para utilizar el CsvDataLoader. Solo debemos pasarle el path al CsvDataLoader, y luego pasarle ese loader al DbConnectionFactory.

     var path = Path.Combine(Directory.GetCurrentDirectory(), "SampleData");
     var loader = new Effort.DataLoaders.CsvDataLoader(path);
     _context = new TechiesDbContext(Effort.DbConnectionFactory.CreateTransient(loader));
    

  1. Con esto nuestra conexión iniciara con los datos de ejemplo que están en los archivos csv que residen en la carpeta que especificamos en el data loader. Así que podemos escribir nuestra prueba unitaria para verificar que se devuelven correctamente los datos.

     [TestMethod]
     public void GetById_returnsEnity_whenItExists()
     {
      //Arrange
      var techies = new Techies(_context);
      var realId = 2;
         
      //Act
      var techie = techies.GetTechieById(realId);
    
      //Assert
      Assert.IsNotNull(techie);
      Assert.AreEqual(techie.Id, realId);
     }
    

  1. Ejecutando nuestra prueba podemos ver que los datos son devueltos correctamente.

Conclusiones

El proyecto Effort es un interesante paquete que nos permite hacer pruebas unitarias para aquellos componentes que dependen de Entity Framework, puede ser usado también para crear Moqs de componentes que usan Entity Framework facilitándonos la carga de datos de ejemplo consistentes con la realidad.

Si les interesa el proyecto pueden revisar el código fuente en Github https://github.com/tamasflamich/effort y si desean probar Effort también pueden descargar el código fuente del proyecto que use en esta demo en GitHub.