Árvore de páginas

Versões comparadas

Chave

  • Esta linha foi adicionada.
  • Esta linha foi removida.
  • A formatação mudou.
Índice
Introdução

Testes de integração compreendem a segunda fase do ciclo de testes de uma aplicação.

Após testar cada unidade o próximo passo é testar um grupo de funcionalidades, como um todo, fazendo a integração de todos os módulos da aplicação.

Para este tipo de teste o TNF traz algumas classes e definições na hora da criação do seu teste integrado.

As estruturas de asserts dos testes descritos abaixo foram criados usando o Xunit e o Shouldly.

Testes na camada de aplicação

Para criar testes dessa camada temos que analisar o que sua aplicação utiliza como infraestrutura.

Testando a camada de aplicação com Entity Framework Core em memória.

Considere para os cenários a seguir a seguinte tabela criada no Entity Framework Core e seu DTO

Bloco de código
languagec#
firstline1
titleCountry.cs
linenumberstrue
[AutoMap(typeof(CountryDto))]
[Table("Countries")]
public class Country : Entity
{
	public const int MaxNameLength = 256;
    [Required]
    [MaxLength(MaxNameLength)]
    public string Name { get; set; }
    public Country()
    {
    }
    public Country(int id, string name)
    {
    	Id = id;
        Name = name;
    }
}
 
public class CountryDto
{
	public string Name { get; set; }
}

Considere a estrutura de testes apresentada no capitulo anterior: Testes#Cenáriodetestes

Criando a estrutura de teste

Vamos começar criando os projetos onde estarão nossos testes integrados:

Para testes na camada de aplicação

Em nosso visual studio: File -> New -> Project e vamos escolher a opção Class Library (.NET Core)

Para utilizar os frameworks de teste instale os seguintes pacotes via nuget:

  • Microsoft.NET.Test.Sdk
  • NSubstitute
  • Shouldly
  • Tnf.App.EntityFrameworkCore.TestBase
  • xunit
  • xunit.runner.visualstudio

Para testes na camada de serviços

Em nosso visual studio: File -> New -> Project e vamos escolher a opção Class Library (.NET Core)

Para utilizar os frameworks de teste instale os seguintes pacotes via nuget:

  • Microsoft.NET.Test.Sdk
  • Microsoft.AspNetCore.TestHost
  • NSubstitute
  • Shouldly
  • Tnf.App.AspNetCore.TestBase
  • Tnf.App.EntityFrameworkCore.TestBase
  • xunit
  • xunit.runner.visualstudio

Para adicionar pacotes do TNF adicione nos sources do nuget o endereço: https://www.myget.org/F/tnf/api/v3/index.json

Testes na camada de aplicação

Testes com Entity Framework Core em memória.

Se você construiu uma aplicação onde tenha utilizado uma infraestrutura com Entity Framework Core podemos utilizar um banco em memoria para realizar nossos testes integradosPara realizar teste em cenários com o uso de Entity Framework Core em memórioa o TNF prove alguns facilitadores.

Vamos começar com a criação do modulo que fará o carregamento da estrutura de testes:

Bloco de código
languagec#
firstline1
titleAppTestModule
linenumberstrue
[DependsOn(
	typeof(AppModule),
    typeof(TnfTestBaseModuleTnfAppTestBaseModule))]
public class AppTestModuleEfCoreAppTestModule : TnfModule
{
	public override void PreInitialize()
    {
    	Configuration.Modules
        	.TnfEfCoreInMemory(IocManager.IocContainer)
            .RegisterDbContextInMemory<ArchitectureDbContext>();
	}
    
    public override void Initialize()
	{
    	IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
    }
}

O modulo "AppTestModuleEfCoreAppTestModule" foi criado no projeto de testes sendo responsável por carregar toda a estrutura de sua aplicação para o teste integrado quando você trabalhar com testes e Entity Framework em memoria.

Note que o atributo do modulo "DependsOn" tem como referencia outro modulo chamado "AppModule". Este modulo é um modulo da camada de aplicação de nossa aplicação concreta a ser testada. Esse modulo também contém , contendo dependências de outras camadas como domínio e infraestrutura onde as entidades estão sendo persistidas com o Entity Framework Core.

Também temos como dependência o modulo "TnfAppTestBaseModule" do TNF carregando toda a estrutura de testes integrados.

O código exemplificado acima, carrega o modulo da camada a ser testada, configurando no método "PreInitialize" o uso do Entity Framework Core em memoria para o DbContext "ArchitectureDbContext" . Essa configuração irá habilitar o uso da estrutura em memoria podendo assim criar um setup em cada teste criado onde será possível incluir a estrutura em memoria no ORM (setup) executando assim o teste em cima da camada de serviço.através do método "RegisterDbContextInMemory".

Definido o modulo de teste vamos criar nosso classe de setup chamada "EfCoreAppTestBase" realizando a herança da classe Para criação do Setup de cada teste o TNF disponibiliza uma classe abstrata chamada "TnfEfCoreIntegratedTestBase<Module>" que recebe um TnfModule ("AppTestModule" vide exemplo anteriorimplementado anteriormente):

Bloco de código
languagec#
firstline1
titleAppTestBase.cs
linenumberstrue
public class AppTestBaseEfCoreAppTestBase : TnfEfCoreIntegratedTestBase<AppTestModule>TnfEfCoreIntegratedTestBase<EfCoreAppTestModule>
{
	protected override void InitializeIntegratedTest()
    {
    	UsingDbContext<ArchitectureDbContext>(
        	context =>
            {
            	context.Countries.Add(new Country(1, "Brasil"));
                context.Countries.Add(new Country(2, "EUA"));
                context.Countries.Add(new Country(3, "Uruguai"));
                context.Countries.Add(new Country(4, "Paraguai"));
                context.Countries.Add(new Country(5, "Venezuela"));
            });
	}
}

A classe TnfEfCoreIntegratedTestBase está contida no pacote Tnf.App.EntityFrameworkCore.TestBase em nosso feed: "https://www.myget.org/F/tnf/api/v3/index.json"TestBase.

Essa classe força a implementação do método "InitializeIntegratedTest" onde será definido o setup de nosso contexto do Entity Framework Core em memoria. dados.

A mesma classe expõe alguns métodos para inclusão de dados na memoria do contexto como o do podemos observar no exemplo acima usando a instrução "UsingDbContext<DbContext>".

Definida a classe que fará o setup de cada teste criado, podemos de setup, vamos começar a escrita de nossos testes sobre os serviços de aplicação:

Bloco de código
languagec#
firstline1
titleICountryAppService.cs
linenumberstrue
public interface ICountryAppService : IAsyncCrudAppService<CountryDto>
{
}
Bloco de código
languagec#
firstline1
titleCountryAppService.cs
linenumberstrue
public class CountryAppService : AsyncCrudAppService<Country, CountryDto>, ICountryAppService
{
	public CountryAppService(IRepository<Country> repository)
    	: base(repository)
	{
    }
}

O serviço de aplicação do exemplo acima utiliza o AsyncCrudAppService do TNF que cria a estrutura de CRUD automaticamente.Após criar nosso serviço de aplicação, podemos começar o desenvolvimento dos testes:

Bloco de código
languagec#
firstline1
titleCountryAppServiceTests.cs
linenumberstrue
public class CountryAppServiceTests : AppTestBaseEfCoreAppTestBase
{
	private readonly ICountryAppService _countryAppService;
    public CountryAppServiceTests()
    {
    	_countryAppService = LocalIocManager.Resolve<ICountryAppService>();
	}


	[Fact]
    public void Service_Should_Not_Be_Null()
    {
    	_countryAppService.ShouldNotBeNull();
    }
 
	[Fact]
	public async Task Create_Item_With_Sucess()
    {
    	var result = await _countryAppService.Create(new CountryDto()
        {
        	Id = 6,
            Name = "Mexico"
		});
		result.Name.ShouldBe("Mexico");
	}
 
	[Fact]
    public async Task Get_Return_Item_With_Sucess()
    {
 		var result = await _countryAppService.Get(new EntityDto<int>(1));
        result.Id.ShouldBe(1);
        result.Name.ShouldBe("Brasil");
	}
}

Note que no exemplo acima Acima realizamos a herança da nossa classe que realiza o setup dos dados em nosso contexto em memoria. Agora podemos Podemos agora executar nossos testes em nosso serviço de aplicação normalmente em cima da estrutura em memoria do Entity Framework Core.

Cada teste executado no cenário acima será de forma reproduzido de forma isoladareproduzido de forma isolada.

Todo teste executado contém seu próprio contexto do Entity Framework Core rodando separadamente dos demais.

Testes com NSubstitute

Quando estamos trabalhando com repositórios sem o uso de um ORM, precisamos fazer o mock da interface do repositório, simulando um cenário afim de explorar todo o funcionamento do sistema.

Em nosso caso vamos testar a infraestrutura do Carol de nosso cenário de testes.

Para isso o TNF possui uma extensão onde é possível realizar a substituição de uma injeção usando o NSubstitute para criar objeto de mock no injetor de dependência.

Vamos criar o modulo que irá carregar a estrutura de nosso teste e fazer a substituição do serviço no injetor de dependência.

Bloco de código
languagec#
firstline1
titleAppTestModule.cs
linenumberstrue
[DependsOn(
	typeof(AppModule),
    typeof(TnfAppTestBaseModule))]
public class NSubstituteAppTestModule : TnfModule
{
	public override void PreInitialize()
	{
		// Mock repositories
        Configuration.ReplaceService<IWhiteHouseRepository>(() =>
        {
        	var instance = Substitute.For<IWhiteHouseRepository>();
            
            var presidentToInsert = new PresidentDto("1", "New President", "55833479");
    
            instance.InsertPresidentsAsync(Arg.Any<List<PresidentDto>>)
				.Returns(Task.FromResult(presidentToInsert));
 
			IocManager.IocContainer.Register(
            	Component
                	.For<IWhiteHouseRepository>()
                    .Instance(instance)
                    .LifestyleTransient()
			);
		});
	}

    public override void Initialize()
	{
		IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
	}
}

O modulo "NSubstituteAppTestModule" foi criado no projeto de testes para carregar toda a estrutura de sua aplicação para o teste integrado.

Note que o atributo do modulo "DependsOn" tem como referencia outro modulo chamado "AppModule", carregando a camada de aplicação concreta a ser testada. Esse modulo também contém dependências de outras camadas como domínio e infraestrutura onde estão definidos nossos repositórios de dados.

Também temos como dependência o modulo "TnfAppTestBaseModule" para carregar a estrutura de testes integrados.

O código exemplificado acima, carrega o modulo da camada a ser testada, configurando no método "PreInitialize" com os objetos de mock. No nosso exemplo estamos mocando através da função ReplaceService usando o framework NSubstitute para criar um objeto de mock para esse repositório e registrá-lo no container de injeção de dependência.

Após a definição do módulo vamos criar a classe de Setup para cada teste integrado: para isso a classe abaixo nomeada de "NSubstituteAppTestBase" realiza a herança da classe "TnfAppIntegratedTestBase<Module>" que recebe um TnfModule (implementado anteriormente):

Bloco de código
languagec#
firstline1
titleAppTestBase.cs
linenumberstrue
public class NSubstituteAppTestBase : TnfAppIntegratedTestBase<NSubstituteAppTestModule>
{
}

A classe TnfAppIntegratedTestBase está contida no pacote Tnf.App.TestBase.

Definido nosso setup podemos implementar nossa classe e realizar os testes usando nosso repositório criado com NSubstitute.

Bloco de código
languagec#
firstline1
titleWhiteHouseAppService
linenumberstrue
public class WriteHouseAppServiceTests : NSubstituteAppTestBase
{
	private readonly IWhiteHouseAppService _whiteHouseAppService;

    public WriteHouseAppServiceTests()
    {
    	_whiteHouseAppService = LocalIocManager.Resolve<IWhiteHouseAppService>();
	}
 
	[Fact]
    public async Task Should_Insert_President_With_Success()
    {
    	// Act
        var response = await _whiteHouseAppService.InsertPresidentAsync(new PresidentDto("1", "New President", "12345678"));
		
		// Assert
        Assert.False(LocalNotification.HasNotification());
	}
}

Podemos perceber que toda a estrutura de Injeção de dependência ainda é mantida inclusive nos testes.

O objeto LocalIocManager contém um wrapper que pode ser acessado para resolver toda a dependência das camadas carregadas em nosso teste através de nosso modulo que foi definido.

Com o NSubstitute criamos o objeto fazendo sua substituição dentro da estrutura de IoC do framework.

Assim temos o teste simulando toda a pilha de dependências do teste integrado.

Testes na camada de serviços

Todos os testes integrados visto até o presente momento podem ser aplicados no teste da camada de serviço.

O teste desta camada é muito parecido aos testes anteriores de aplicação porque adiciona mais um nível ao teste que é exatamente onde a exposição da camada de aplicação ocorre.

Para testar nossa camada de serviços podemos unir os dois conceitos de testes partindo do principio que nossa aplicação pode ter N fontes de dados (Infraestrutura) e independente de qual sejam elas, precisamos criar testes para validar nossa regra de negocio.

Como nossa camada de serviço expõe toda uma infraestrutura usando fontes como Entity Framework Core e Carol, precisamos utilizar o Entity Framework Core em memoria em conjunto com objetos de mock.

Vamos começar com a criação de nosso modulo que fará o carregamento da estrutura dos testes:

Bloco de código
languagec#
firstline1
titleAppTestModule.cs
linenumberstrue
[DependsOn(
	typeof(AppModule),
    typeof(TnfAppAspNetCoreTestBaseModule))]
public class AppTestModule : TnfModule
{
	public override void PreInitialize()
    {
    	Configuration.Auditing.IsEnabledForAnonymousUsers = true;

        // Mock repositories
        Configuration.ReplaceService<IWhiteHouseRepository, WhiteHouseRepositoryMock>();

		Configuration.Modules
        	.TnfEfCoreInMemory(IocManager.IocContainer, IocManager.Resolve<IServiceProvider>())
            .RegisterDbContextInMemory<ArchitectureDbContext>();
        }

        public override void Initialize()
        {
            IocManager.RegisterAssemblyByConvention(typeof(AppTestModule).GetAssembly());
        }
    }
}

Note que nosso module ainda contem a mesma estrutura de antes usando a função ReplaceService para substituir o repositório que será injetado (nesse caso para elucidar não foi usado o NSubstitute, foi criado uma classe concreta, mas nada impede que ele seja usado como nos exemplos anteriores) e configurado o Entity Framework Core para trabalhar em memoria.

Nosso modulo agora contem dependências diferente pois vamos testar uma camada de AspNetCore e por isso devemos deixar explicito em nosso atributo "DependsOn" o uso do modulo TnfAppAspNetCoreTestBaseModule.

Como estamos criando um teste para uma aplicação ASP .NET Core que expõe nossas APIs, temos que criar uma classe de startup que fará a configuração de nosso pipeline:

Bloco de código
languagec#
firstline1
titleStartupTest.cs
linenumberstrue
public class StartupTest
{
	public IServiceProvider ConfigureServices(IServiceCollection services)
    {
    	services
            .AddMvcCore()            .AddApplicationPart(typeof(Tnf.Architecture.Web.Startup.WebModule).GetAssembly())
            .AddControllersAsServices();

		services.AddEntityFrameworkInMemoryDatabase();

        // Configure Tnf and Dependency Injection
        return services.AddTnfApp<AppTestModule>(options =>
        {
        	// Test setup
            options.SetupTest();
		});
	}

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
    	app.UseTnf(); //Initializes Tnf framework.
        app.UseMvc(routes =>
        {
        	routes.MapRoute(
            	name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
	}
}

No exemplo acima podemos notar algumas coisas que foram configuradas:

  • AddApplicationPart para TnfAspNetCoreModule e Tnf.Architecture.Web.Startup.WebModule: Esse método informa a configuração do AspNetCore que existem partes da aplicação em outro assembly;
  • AddControllersAsServices: Essa configuração carrega toda nossa estrutura dos assemblies registrados como "AddApplicationPart" e os torna visíveis dentro de nosso cenário de testes;
  • AddEntityFrameworkInMemoryDatabase: Neste ponto configuramos o uso do Entity Framework em memoria para o pipeline do AspNetCore;
  • AddTnfApp: adicionamos o suporte ao TNF, deixando explicito que estamos criando uma aplicação AspNetCore para testes passando nossa configuração de modulo (AppTestModule definido anteriormente);

Com nossa classe de startup criada podemos definir a classe de setup de cada teste integrado usando AspNetCore, Entity Framework em memoria e objetos mock:

Bloco de código
languagec#
firstline1
titleAppTestBase.cs
linenumberstrue
public abstract class AppTestBase : TnfAspNetCoreIntegratedTestBase<StartupTest>
{
	protected override void InitializeIntegratedTest()
    {
    	IocManager.UsingDbContext<ArchitectureDbContext>(
        	context =>
            {
            	context.Countries.Add(new Country(1, "Brasil"));
                context.Countries.Add(new Country(2, "EUA"));
                context.Countries.Add(new Country(3, "Uruguai"));
                context.Countries.Add(new Country(4, "Paraguai"));
                context.Countries.Add(new Country(5, "Venezuela"));
            });
	}
}

Note que aqui usamos para cada teste a herança da classe TnfAspNetCoreIntegratedTestBase<Startup>. 

Essa classe auxilia em cenários de teste como esse onde precisamos testar um contexto AspNetCore, recebendo como parâmetro genérico a classe de startup com as configurações da aplicação de teste.

Essa classe contém um método que pode ser sobrescrito chamado "InitializeIntegratedTest", onde é possível realizar a configuração (setup) de cada teste executado.

No exemplo acima vemos que através do objeto IocManager conseguimos acessar o método UsingDbContext<DbContext> que recebe como parâmetro genérico o DbContext, para inserir nossos dados em memória.

Os objetos de mock foram configurados dentro do nosso modulo através de uma implementação concretra, mas como visto em testes anteriores podemos fazer o uso do NSubstitute.

Vamos agora definir uma classe de teste e realizar o teste em cima de nossa API:

Bloco de código
languagec#
firstline1
titleWhiteHouseControllerTests.cs
linenumberstrue
public class WhiteHouseControllerTests : AppTestBase
{
	[Fact]
    public void Should_Resolve_Controller()
    {
    	ServiceProvider.GetService<WhiteHouseController>().ShouldNotBeNull();
	}
 
	[Fact]
	public async Task GetAll_Presidents_With_Success()
	{
        // Act
        var response = await GetResponseAsObjectAsync<ListDto<PresidentDto>>(
                           $"{RouteConsts.WhiteHouse}?pageSize=5",
                           HttpStatusCode.OK
                       );

        // Assert
        Assert.Equal(response.Items.Count, 5);
	}
 
	[Fact]
	public async Task GetAll_Presidents_With_Invalid_Parameters_Return_Bad_Request()
	{
        // Act
        var response = await GetResponseAsObjectAsync<ErrorResponse>(
                          $"{RouteConsts.WhiteHouse}",
                          HttpStatusCode.BadRequest
                       );

        // Assert
        response.Message.ShouldBe("GetAllPresident");
        response.DetailedMessage.ShouldBe("GetAllPresident");
        Assert.True(response.Details.Any(n => n.Message == Error.InvalidParameter.ToString()));
	}
}

Em nossa classe de teste temos acesso a alguns métodos pela herança da classe TnfAspNetCoreIntegratedTestBase contida dentro do pacote Tnf.App.AspNetCore.TestBase para criação de testes em serviços.

Entre os métodos disponíveis podemos listar:

  • GetResponseAsObjectAsync
  • PostResponseAsObjectAsync
  • PutResponseAsObjectAsync
  • DeleteResponseAsObjectAsync

Esses métodos realizam a conversão da resposta para o formato esperado, onde é informado a url e o status Http de retorno esperado.

Em conjunto a isso nos testes acima foram usados o Xunit e o Shouldly, adicionando ainda mais suporte na hora de realizar as asserções em nossos retornos.

Neste teste temos o nível de integraçãomáxima de nossa arquitetura, integrando as camadas de serviço, aplicação, domínio e infraestrutura.