Arquitectura, buenas prácticas y desarrollo sobre la nueva herramienta de Microsoft SharePoint 2016

¿Qué herramientas usamos para hacer test de integración en DotNet?

A la hora de empezar con los Test de Integración tenemos varias formas de realizarlos. En este artículo os enseñaré las utilidades disponibles para hacer testing de integración desde Visual Studio y con DotNet Core.

¿Qué entiendo por Test de Integración?

Según unas de las definiciones que nos encontramos por Internet, los test de integración son aquellos que se realizan en el ámbito del desarrollo de software una vez se han aprobado las pruebas unitarias, y sirven para comprobar que todos los elementos unitarios que componen el software funcionan correctamente al probarlos en grupo.

Partiendo de esta definición vamos a un escenario “común”. Este escenario sería una API Rest en la cual queremos testear todos los endpoint. Por ejemplo, tenemos una API que nos devuelve todos nuestros Avengers favoritos tal y como se visualiza aquí:

Solución Visual Studio

API que nos devuelve todos nuestros Avengers favoritos

Antes de empezar con las herramientas vamos a descifrar qué elementos son los que tiene esta solución y así, podremos ver cómo lo testearemos y qué herramienta usaremos. Recapitulando:

  1. Sistema de autenticación contra el Azure Active Directory
  2. Base de Datos SQL Azure usando Entity Framework

¿Qué debemos testear y cómo debemos hacerlo?

Para la parte del sistema de autenticación, al final lo que debemos de cubrir en un test en el que alguien que no esté autenticado cuando intente llamar a nuestra API, le dé un código 401 Unauthorized; y cuando esté autenticado, que el token tenga la información necesaria para que nuestra aplicación funcione. Por lo tanto, tendremos que tener “algo” que nos simule dicho comportamiento.

Para ello, en primer lugar, .NET Core nos trae una herramienta Test Host en la que desde un proyecto de Test podemos levantar nuestra aplicación e indicarle en el StartUp la configuración que vamos a lanzar desde el proyecto de Test. Aunque como veremos después, en el código es una librería muy potente y que trae muchísimas utilidades, para el tema de la seguridad deja al usuario la libertad de cómo obtener los tokens JWT. Pues bien, hay una librería OpenSource Acheve implementada por Hugo Biarge que nos facilita la vida.

Ahora bien, para la segunda parte, el tema de la base de datos siempre es algo que hay que tener en cuenta. Si va a ser un test de integración deberemos de ejecutarlo en una base de datos y no podremos mockearla. Por otra parte, estos Test ¿en qué momento se van a ejecutar o quién los van a ejecutar? Dependiendo de las contestaciones tenemos varias alternativas, todas válidas

  • Hacer uso de una base de datos InMemory. Su principal beneficio es que no tiene coste. Es rápida de montar pero, por contra, hay determinadas acciones en las que no se comporta como una base de datos real.
  • Levantar una base de datos en un contenedor (Docker). Aquí su principal ventaja es que el comportamiento exactamente igual que la base de datos, pero su principal inconveniente es que si lo montamos en una Build, hay que sumar el tiempo de levantar el docker a la duración de la Build y dependiendo de qué escenarios, puede ser un stoper.
  • Utilizar una base de datos “real” y usar alguna herramienta que nos deje la base de datos tal y como estaba inicialmente (si estáis interesados en esta opción no me perdería la sesión que impartió Nacho Fanjul en la NetCoreConf).

Show me the Code

Una vez realizadas las aclaraciones sobre cómo vamos a librar algunos inconvenientes a la hora de realizar los Test, centrémonos en lo importante en el Test y cómo implementarlo.

XUnit

La herramienta para realizarlos es xUnit, atrás han quedado los tiempos de MS-Test y .NET Core. XUnit nos proporciona una librería de Test que si se utiliza correctamente es bastante potente.

En primer lugar, vamos a crear nuestro servidor de Test. Para ello tendremos que crear un Startup que será el punto de arranque.

Creamos un arranque que herede el StartUp que tenemos en la API y en dicho arranque,  sobreescribimos los métodos que vamos a realizar en los test.

public class TestStartup : Startup
   {
       public TestStartup(IConfiguration configuration, IWebHostEnvironment environment) : base(configuration, environment)
       {
       }
   }

Tal y como tenemos montado el StartUp tenemos un método donde Configuramos la Autenticación y ahora para el test lo que vamos a realizar es indicarle que coja un esquema que está dentro del Test Server (en lugar del esquema que tenemos puesto en la API ).

        protected override void ConfigureAuthentication(IServiceCollection services)
       {
           services.AddMvc().AddApplicationPart(typeof(AvengerController).Assembly);
           services.AddAuthentication(options =>
           {
               options.DefaultScheme = TestServerDefaults.AuthenticationScheme;
           })
           .AddTestServer();
       }

El código expuesto no tiene mucho misterio, solo la primera línea. Esto lo que hará es que cuando realicemos una petición usando el TestServer, podamos invocar directamente a la clase del controlador en lugar de a la url que se levanta.

Una vez tenemos resuelto el tema de la autenticación, vamos con la parte de la base de datos. En este caso vamos a utilizar un SQLLite que se genera en memoria. Para ello pondremos el siguiente código:

   protected override void ConfigureBD(IServiceCollection services)
       {
           var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
           var connectionString = connectionStringBuilder.ToString();
           var connection = new SqliteConnection(connectionString);
           services
             .AddEntityFrameworkSqlite()
             .AddDbContext<AppDbContext>(
               options => options.UseSqlite(connection)
             );
       }

Vale, la base de datos ya la tenemos in Memory, pero muchas veces, de cara a los test, es interesante que tengamos datos ya cargados de pruebas. Para ello, vamos a hacer uso de Bogus,  una librería que nos permite multitud de opciones para crear datos de test o de pruebas muy útiles. Para ello, en el mismo método añadimos la siguiente funcionalidad para insertar los datos (se podría usar el Seed propio de EF pero si es algo que no lo vamos a tener en el código de producción, a mí personalmente me gusta más separarlo por dejarlo más limpito):

var serviceProvider = services.BuildServiceProvider();
var storeContext = serviceProvider.GetRequiredService<AppDbContext>();
   storeContext.Database.OpenConnection(); 
   storeContext.Database.EnsureCreated();
   storeContext.Database.Migrate();
   if (!storeContext.Avenger.Any())
   {
       var avengerList = new Faker<Avenger>().Generate(10);
       storeContext.AddRange(avengerList);                              
       storeContext.SaveChanges();
       }

Una de las cosas más chulas que tiene xUnit es la posibilidad de inyectar a tus pruebas un estado previo en el que se encuentra. Esto podríamos decir que es el TestFixture. Creamos un TestFixture en el que ponemos el Test de Pruebas y también aquellos aspectos que serán necesarios para los test. En este caso, yo me he creado una Claim que será la que utilice para la autenticación.

   public class TestFixture : IDisposable
   {
       public TestServer TestServer { get; private set; }
       public HttpClient Client { get; private set; }
       public string TenantIdClaimType => "http://schemas.microsoft.com/identity/claims/tenantid";        

       public List<Claim> DefaultIdentity
       {
           get
           {
               return new List<Claim>()
               {
                   new Claim(ClaimTypes.Name, "Hulk"),
                   new Claim(TenantIdClaimType, "3ab8g5de-32c5-3134-zfdarf-182a67a344"),
                   new Claim(ClaimTypes.Upn, "hulk@theavenger.com")
               };
           }
       }
       public TestFixture()
       {
           CreateServer();
       }
       private void CreateServer()
       {
           var path = Assembly.GetAssembly(typeof(TestFixture))
               .Location;

           Log.Logger = new LoggerConfiguration()
               .MinimumLevel.Verbose()
               .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
               .Enrich.FromLogContext()
               .WriteTo.Console()
               .CreateLogger();

           var hostBuilder = new WebHostBuilder()
               .UseContentRoot(Path.GetDirectoryName(path))
               .ConfigureServices(services=>
               {
                   services.AddAutofac();
               })                
               .UseSerilog()
               .ConfigureAppConfiguration(cb =>
               {
                   cb.AddJsonFile("appsettings.json", optional: false)
                   .AddEnvironmentVariables();
               })
               .UseStartup<TestStartup>();            
           this.TestServer = new TestServer(hostBuilder);
           this.Client = TestServer.CreateClient();
       }

       public void Dispose()
       {
           if (Client != null)
           {
               Client.Dispose();
           }
           if (TestServer != null)
           {
               TestServer.Dispose();
           }
       }
   }

Una vez ya tenemos creado el TestServer y el TestFixture, vamos a lo que realmente importa los Test. En primer lugar y por convención, a cada uno de los controladores de nuestra API que vamos a testear los llamaremos escenarios. Nos creamos una clase en la que le inyectemos la Fixture. Para ello tendremos algo similar al siguiente código:

    [Trait("Category", "Integration")]
    public class AvengerScenarios : IClassFixture<TestFixture>
    {
        private readonly TestFixture fixture;

        public AvengerScenarios(TestFixture fixture)
        {
            this.fixture = fixture;            
        }

    }

Luego implementaremos el Test. Por ejemplo, vamos a testear que el método que nos devuelve a todos los Avengers. Tenemos que asegurarnos que está autenticado, para lo cual usaremos el método WithIdentity que nos proporciona Acheve. Más tard, usaremos la librería Fluent Assertions. El principal motivo es por la legibilidad de los test, ya que los hace mucho más similares al lenguaje humano.

      [Fact]
        public async Task Get_AllAvengers_returnOk()
        {
            var response = await fixture.TestServer
                .CreateHttpApiRequest<AvengerController>(controller => controller.Get())
                .WithIdentity(fixture.DefaultIdentity)
                .GetAsync();
            response.StatusCode.Should().Be(HttpStatusCode.OK);
        }

Resumen

En este articulo hemos visto el Tooling de herramienta que utilizo para los Test de Integración.

Acheve.TestHost Bogus FluenAsserttions Microsoft.AspNetCore.TestHost Microsoft.EntityFramewokcore.SQlite Microsoft.EntityFramewokcore.InMemory xUnit

Este tipo de Test también se pueden hacer con herramientas como Postman, sin embargo, yo creo que desde Visual Studio (y tal y como esta planteada esta forma), nos aporta suficiente valor, por ejemplo, cuando se cambia de método el controlador el Test directamente no compila. En cambio, si lo lanzamos desde un Postman, la primera vez que lo lancemos nos devolverá un BadRequest, y es un tiempo de cambio y de rehacer el test, que no es tan fácil.

Junto con estas herramientas hay alguna otra herramienta como Moq.Net, o Coverlet que también utilizamos para otro tipo de Test o para obtener el porcentaje de cobertura de nuestros Test. Pero como muchas veces digo, el porcentaje no es algo que me importe, sino más bien que tengamos testeadas las partes core de la aplicación.

¿Y vosotros cuál utilizáis? ¿Cuál echas de menos?

Happy Codding

mm

Sobre Adrián Díaz

Adrián Díaz es Ingeniero Informático por la Universidad Politécnica de Valencia. Es MVP de Microsoft en la categoría Office Development desde 2014, MCPD de SharePoint 2010, Microsoft Active Profesional y Microsoft Comunity Contribuitor 2012. Cofundador del grupo de usuarios de SharePoint de Levante LevaPoint. Lleva desarrollando con tecnologías Microsoft más de 10 años y desde hace 3 años está centrado en el desarrollo sobre SharePoint. Actualmente es Software & Cloud Architect Lead en ENCAMINA.
Esta entrada ha sido publicada en desarrollo. Enlace permanente.
Suscríbete a Desarrollando sobre SharePoint

Suscríbete a Desarrollando sobre SharePoint

Recibe todas las actualizaciones semanalmente de nuestro blog

You have Successfully Subscribed!

ENCAMINA, piensa en colores