Árvore de páginas

Você está vendo a versão antiga da página. Ver a versão atual.

Comparar com o atual Ver Histórico da Página

« Anterior Versão 14 Próxima »

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.

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.TestBase.App
  • Tnf.App.EntityFrameworkCore.TestBase
  • Microsoft.EntityFrameworkCore.InMemory
  • 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.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.

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

AppTestModule
[DependsOn(
	typeof(AppModule),
    typeof(TnfTestBaseModule))]
public class EfCoreAppTestModule : TnfModule
{
	public override void PreInitialize()
    {
    	Configuration.Modules
        	.TnfEfCoreInMemory(IocManager.IocContainer)
            .RegisterDbContextInMemory<ArchitectureDbContext>();
	}
    
    public override void Initialize()
	{
    	IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
    }
}

O modulo "EfCoreAppTestModule" foi criado no projeto de testes sendo responsável por 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". Este modulo é um modulo da camada de aplicação concreta a ser testada, 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 "TnfTestBaseModule" 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 em memoria para o DbContext "ArchitectureDbContext" através do método "RegisterDbContextInMemory".

Após a definição de nosso módulo vamos criar a classe de Setup para cada teste integrado usando Entity Framework Core em memoria.

Para isso a classe abaixo nomeada de "EfCoreAppTestBase" realiza a herança da classe "TnfEfCoreIntegratedTestBase<Module>" que recebe um TnfModule (implementado anteriormente):

AppTestBase.cs
public class EfCoreAppTestBase : 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.

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

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

CountryAppServiceTests.cs
public class CountryAppServiceTests : EfCoreAppTestBase
{
	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 realizamos a herança da nossa classe que realiza o setup dos dados em nosso contexto em memoria. Agora podemos 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 isolada.

Cada 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 teste funcionalidades usando 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 e usar 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.

AppTestModule.cs
[DependsOn(
	typeof(AppModule),
    typeof(TnfTestBaseModule))]
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 sendo responsável por 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". Este modulo é um modulo da 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 "TnfTestBaseModule" 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" com os objetos de mock. No nosso exemplo estamos mocando através da função ReplaceService do TNF 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 de nosso 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 "TnfEfCoreIntegratedTestBase<Module>" que recebe um TnfModule (implementado anteriormente):

AppTestBase.cs
public class NSubstituteAppTestBase : TnfIntegratedTestBase<NSubstituteAppTestModule>
{
}

A classe TnfIntegratedTestBase está contida no pacote Tnf.TestBase.

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

WhiteHouseAppService
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.True(response.Success);
	}
}

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 dependencia das camadas carregadas em nosso teste através de nosso modulo que foi definido.

Com o NSubstitute criamos o objeto e então fazemos sua substituição dentro da estrutura de IoC do framework, possibilitando assim consumir nosso mock.

Assim temos o teste simulando toda a pilha de dependências do teste integrado. Cada teste executado no cenário acima será de forma reproduzido de forma isolada.

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.

Recapitulando o que vimos até o presente momento em nossos testes integrados: testamos a nossa camada de aplicação utilizando banco em memoria (Entity Framework Core) e usando uma estrutura de mock (NSubstitute).

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.

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

AppTestModule.cs
[DependsOn(
	typeof(AppModule),
    typeof(TnfAspNetCoreTestBaseModule))]
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 repositorio que será injetado (nesse caso para elucidar não foi usado o NSubstitute, foi criado uma classe concreta) 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 TnfAspNetCoreTestBaseModule.

Como estamos criando um teste para um contexto web temos que criar uma classe de startup que fará a configuração de nosso pipeline para AspNetCore:

StartupTest.cs
public class StartupTest
{
	public IServiceProvider ConfigureServices(IServiceCollection services)
    {
    	services.AddMvc()
        	.AddApplicationPart(typeof(TnfAspNetCoreModule).GetAssembly())
            .AddApplicationPart(typeof(Tnf.Architecture.Web.Startup.WebModule).GetAssembly())
            .AddControllersAsServices();

		services.AddEntityFrameworkInMemoryDatabase();

        //Configure Tnf and Dependency Injection
        return services.AddTnf<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?}");
        });
	}
}

Dessa forma podemos criar um setup para cada teste integrado que for realizado:

AppTestBase.cs
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 que o TNF disponibiliza chamada TnfAspNetCoreIntegratedTestBase. Essa classe foi criada para auxiliar em cenários de teste como esse onde precisamos testas um contexto AspNetCore.

Essa classe prove algumas facilitadores para realizar chamadas para nossa aplicação web de testes.

 

  • Sem rótulos