Alura > Cursos de Programação > Cursos de .NET > Conteúdos de .NET > Primeiras aulas do curso Testes em .NET: testando integração com banco de dados

Testes em .NET: testando integração com banco de dados

Definindo testes de integração - Apresentação

Olá, estudante! Boas-vindas a mais um curso da formação de testes em .NET. Sou Jeniffer Bittencourt, instrutora na Escola de Programação, mas, se preferir, pode me chamar de Jeni. Serei a pessoa que vai te acompanhar durante todo o curso.

Audiodescrição: Jeniffer é uma mulher branca de cabelos ondulados de tamanho médio, pintados na cor azul turquesa. Tem olhos castanhos e uso óculos de grau de armação redonda. Está com uma camisa azul escura. Ao fundo, estante com decorações e mangás com iluminação azul e rosa.

O que vamos aprender?

Durante esse curso, vamos aprender a:

E tudo isso aplicando na prática, em situações-problema que são bem comuns no dia a dia de testes de integração com banco de dados.

Vamos continuar trabalhando com o projeto Jornada Milhas, que utilizamos no primeiro curso da formação. Porém, agora temos um cenário com banco de dados na aplicação para poder implementar esses testes de integração.

O projeto atualizado para você fazer o download e conseguir acompanhar esse curso está na próxima atividade.

Pré-requisitos

Para que você tenha um melhor aproveitamento do conteúdo desse curso, é importante que tenha feito o curso Testes em .NET: criando testes de unidade com xUnity.

Esse conteúdo também é para você que quer continuar avançando nos seus conhecimentos de teste e quer conhecer bibliotecas e práticas de testes de integração bastante utilizados no mercado.

Participe da comunidade

Antes de dar play no próximo vídeo, temos um recado muito importante para você. Na plataforma Alura, além das videoaulas que você já conhece, contamos com várias atividades durante os cursos.

Temos as atividades de "Para Saber Mais" para você aprofundar seus conhecimentos em temas específicos e também as atividades de "Mão na Massa" para você poder praticar tudo o que está aprendendo.

É através dessas atividades que você vai poder reforçar tudo o que está aprendendo e evoluir ainda mais nos seus estudos.

Além disso, você pode sempre contar com o suporte no fórum e com a comunidade no Discord da Alura, onde tem várias outras pessoas que também estão estudando o mesmo conteúdo que você e vão gostar de trocar informações sobre o que estão aprendendo.

Já pega um café ou uma água, se ajeita na cadeira e vamos estudar!

Definindo testes de integração - Iniciando em testes de integração

Vamos continuar trabalhando com o projeto Jornada Milhas, que agora tem um diferencial. Agora estamos persistindo os dados de oferta em um banco de dados relacional.

Todo o projeto inicial atualizado com o qual vamos trabalhar durante este curso estará disponível na atividade de "Preparando o ambiente", para que possa fazer o download e as instalações necessárias para prosseguir nesse curso.

Criando testes de integração

Como estamos trabalhando com banco de dados, é a nossa classe DAL (Data Access Layer ou camada de acesso de dados) que vai manipular todas as informações referentes à oferta.

Vamos abrir o gerenciador de soluções, à esquerda do Visual Studio, para acessar a pasta "Dados", onde encontrar o arquivo OfertaViagemDAL.cs.

O primeiro método dessa classe é chamado Adicionar(). Podemos começar os testes por ele.

OfertaViagemDAL.cs:

public void Adicionar(OfertaViagem oferta)
{
        context.OfertasViagem.Add(oferta);
        context.SaveChanges();
}

Novamente no gerenciador de soluções, no projeto inicial que você já baixou, também temos o projeto de teste de integração já criado em "JornadaMilhas.Test.Integracao", como já aprendemos a criar anteriormente.

Podemos utilizar essa classe padrão, UnitTest1.cs, mas vamos renomeá-la para trabalhar com testes em relação ao método Adicionar().

UnitTest1.cs:

namespace JornadaMilhas.Test.Integracao;

public class UnitTest1
{
    [Fact]
    public void Test1()
    {

    }
}

Basta clicar com o botão direito em cima do nome do arquivo e escolher a opção de renomear (ou atalho "F2"). Vamos seguir o padrão de nomenclatura que já aprendemos anteriormente também. Assim, a nossa classe vai se chamar OfertaViagemDalAdicionar para trazer o nome da classe com a qual vamos trabalhar e o nome do método.

Vamos começar a escrever o nosso primeiro teste? Podemos fazer um teste para verificar se a oferta está sendo adicionada no banco.

Para isso, renomearemos o método público Test1(), que vem como padrão na linha 6, para RegistraOfertaNoBanco().

Nesse método, usaremos o padrão do AAA (lê-se triplo A), seguindo os passos de arrange (preparar), act (agir) e assert (verificar).

OfertaViagemDalAdicionar.cs:

public class OfertaViagemDalAdicionar
{
    [Fact]
    public void RegistraOfertaNoBanco()
    {
        //arrange

        //act

        //assert
    }
}

No arrange, podemos começar a trazer os dados que vão construir a nossa oferta.

Para facilitar, vamos colar os dados de rota, período e preço. A rota de São Paulo a Fortaleza, o periodo do dia 20/8 até dia 30/8 e o preco como 350.

Já podemos construir a nossa oferta com esses dados. Na próxima linha, criaremos um var oferta, que será um new OfertaViagem(), onde passaremos a rota, o periodo e também o preco.

Após criar a oferta, precisamos criar o DAL também para poder trabalhar com ele. Também criaremos um var dal, que será um new OfertaViagemDAL().

public class OfertaViagemDalAdicionar
{
    [Fact]
    public void RegistraOfertaNoBanco()
    {
        //arrange
        Rota rota = new Rota("São Paulo", "Fortaleza");
        Periodo periodo = new Periodo(new DateTime(2024, 8, 20), new DateTime(2024, 8, 30));
        double preco = 350

        var oferta = new OfertaViagem(rota, periodo, preco);
        var dal = new OfertaViagemDAL();

        //act

        //assert
    }
}

No act, vamos colocar o que queremos realmente verificar, que é a adição dessa oferta no nosso banco de dados. Devemos verificar no dal chamando o método Adicionar() para adicionar a oferta que acabamos de criar.

Enquanto no assert, durante esse processo de adicionar a oferta, queremos verificar se a nossa oferta foi incluída no banco.

Para isso, podemos usar outro método que também temos no DAL também, que é o de recuperar essa oferta pelo ID.

Primeiro, vamos criar um var ofertaIncluida que recebe o dal.RecuperarPorId(), passando a oferta.Id.

Com essa informação da ofertaIncluida, já conseguimos fazer a verificação. Escreveremos Assert.NotNull(), pois queremos verificar se essa informação de ofertaIncluida não será nula. Se não for nula, é porque existe a oferta incluída no banco.

Também podemos fazer outro assert, um Assert.Equal(), para comparar o valor da oferta com a ofertaIncluida para verificar se foi incluído o valor que realmente queríamos.

Para isso, verificaremos se ofertaIncluida.Preco é igual ao oferta.Preco. Além disso, vamos passar um terceiro parâmetro para delimitar as casas decimais, que será 0.001.

public class OfertaViagemDalAdicionar
{
    [Fact]
    public void RegistraOfertaNoBanco()
    {
        //arrange
        Rota rota = new Rota("São Paulo", "Fortaleza");
        Periodo periodo = new Periodo(new DateTime(2024, 8, 20), new DateTime(2024, 8, 30));
        double preco = 350;

        var oferta = new OfertaViagem(rota, periodo, preco);
        var dal = new OfertaViagemDAL();

        //act
        dal.Adicionar(oferta);

        //assert
        var ofertaIncluida = dal.RecuperarPorId(oferta.Id);
        Assert.NotNull(ofertaIncluida);
        Assert.Equal(ofertaIncluida.Preco, oferta.Preco, 0.001);
    }
}

Criamos o nosso primeiro método de teste que vai verificar esse registro de oferta no banco. Vamos executá-lo?

Abrimos o gerenciador de testes, na lateral direita, e vamos clicar em "Executar todos os testes na exibição" (ou atalho "Ctrl + R, V").

Todos os testes foram executados com sucesso, incluindo o RegistroOfertaNoBanco.

Modificando o contexto

Se analisamos a classe OfertaViagemDAL, reparamos que ela trabalha em cima de um context. E, na linha 16, podemos usar o "Ctrl" e clicar em cima de JornadaMilhasContext() para poder abrir essa classe e verificar também o que tem nesse contexto.

Nesse contexto, temos o endereço do nosso banco de dados, que é o componente externo que estamos testando. Para o nosso teste funcionar corretamente, ele depende diretamente do funcionamento desse componente externo também.

JornadaMilhasContext.cs:

public class JornadaMilhasContext: DbContext
{
    // código omitido…

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=JornadaMilhas;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False");
    }
    // código omitido…
}

O que aconteceria se precisássemos apontar esse endereço para outro banco de dados? Vamos fazer o teste e ver o que acontece?

Na informação do contexto, em OnCofiguring(), onde tem o nome do banco de dados, que é esse Initial Catalog, vamos colocar um Test na frente do JornadaMilhas. Assim, o nome desse outro banco será JornadaMilhasTest.

public class JornadaMilhasContext: DbContext
{
    // código omitido…

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=JornadaMilhasTest;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False");
    }
    // código omitido…
}

Agora, abrimos novamente no gerenciador de testes e executamos todos nossos testes, para verificar o que vai acontecer.

O teste de RegistroOfertaNoBanco falhou, parou de funcionar, porque não está conseguindo achar a conexão com o banco.

Próximos passos

Essa falha que está dando no nosso teste aconteceu sem mudar absolutamente nada no teste. Ele está tendo dois comportamentos diferentes com o mesmo teste. Isso é uma falha em relação à característica determinística que os testes precisam ter.

Precisamos que o nosso teste tenha o mesmo resultado, independente da situação.

Nos testes de unidade, é um pouco mais fácil conseguir garantir essa característica, porque não temos essa relação com componentes externos.

Já nesse caso, que estamos trabalhando com uma relação com o banco de dados, estamos trabalhando com testes de integração, que são testes que verificam essa comunicação e essa integração efetivamente de dois componentes. Nesse caso, estamos trabalhando com a nossa aplicação e o banco de dados.

Existem vários tipos de componentes externos que podemos precisar testar, como, por exemplo, APIs ou outros serviços que a aplicação precisa utilizar. Mas, no caso desse curso, vamos focar exclusivamente nos testes de integração para banco de dados, que é um componente externo muito comum de ser utilizado nesse universo back-end.

No próximo vídeo, vamos começar a entender como controlar a dependência desses componentes externos, mais especificamente, ao banco de dados. Até lá!

Definindo testes de integração - Baixo acoplamento

Estamos enfrentando um problema. Precisamos garantir que nosso teste seja determinístico, ou seja, que funcione independentemente da situação. Para isso, precisamos ter um maior controle sobre nosso componente externo, que neste caso é o banco de dados.

Isolando a conexão do banco de dados

Precisamos trazer a informação da conexão com o banco de dados, que está no nosso JornadaMilhasContext, para dentro do nosso teste, para que o teste consiga controlar esse ambiente.

Podemos copiar essa informação que está no nosso optionsBuilder, de .UseSqlServer até o final da informação de conexão, na linha 24.

Trecho copiado:

.UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=JornadaMilhasTest;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False")

Após copiá-lo, vamos levar para a nossa classe de teste OfertaViagemDalAdicionar.

No teste RegistraOfertaNoBanco(), vamos adicioná-la no arrange, que são as informações que precisamos para poder testar.

Para isso, criaremos um var options que recebe um new DbContextOptionsBuilder, passando o JornadaMilhasContext. Isto é, new DbContextOptionsBuilder<JornadaMilhasContext>().

Feito isso, podemos passar a informação da conexão após os parênteses. Podemos acrescentar o .UseSqlServer() com toda a informação de conexão com o banco, que era o que precisávamos.

Vamos adicionar também mais uma informação, após o fecha parênteses de UseSqlServer(), que será o .Options.

Já temos a informação de conexão com o nosso banco. Não podemos colocar um ponto e vírgula após o UseSqlServer(), senão a próxima informação não será identificada.

O próximo passo é criar o context. Logo abaixo, criamos um var context, que será um new JornadaMilhasContext(), passando a variável options que acabamos de criar.

OfertaViagemDalAdicionar.cs:

[Fact]
public void RegistraOfertaNoBanco()
{
        //arrange
        Rota rota = new Rota("São Paulo", "Fortaleza");
        Periodo periodo = new Periodo(new DateTime(2024, 8, 20), new DateTime(2024, 8, 30));
        double preco = 350;

        var options = new DbContextOptionsBuilder<JornadaMilhasContext>()
                .UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=JornadaMilhasTest;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False")
                .Options;

        var context = new JornadaMilhasContext(options);

        var oferta = new OfertaViagem(rota, periodo, preco);
        var dal = new OfertaViagemDAL();

        // código omitido…
}

Com isso, temos o contexto dentro da classe de teste, mas precisamos também fazer uma alteração no construtor da classe DAL para que ele receba qual contexto queremos utilizar, senão ele vai continuar pegando o contexto padrão do JornadaMilhasContext.

No construtor da classe OfertaViagemDAL, vamos dizer que agora ele sempre vai receber um JornadaMilhasContext, que chamaremos de context.

Dentro do construtor, não vamos mais instanciar um novo contexto. Em lugar disso, teremos um this.context, que será igual ao context.

OfertaViagemDAL.cs:

public class OfertaViagemDAL
{
    private readonly JornadaMilhasContext context;

    public OfertaViagemDAL(JornadaMilhasContext context)
    {
        this.context = context;
    }

    // código omitido…
}

A partir de agora, toda vez que instanciarmos um DAL, precisamos passar um contexto.

De volta na classe de teste, agora temos um erro na linha 24 do arrange, onde temos o dal, porque precisamos passar o context.

OfertaViagemDalAdicionar.cs:

var dal = new OfertaViagemDAL(context);

Enquanto no JornadaMilhasContext, precisamos passar uma condição para ele utilizar essa conexão com o banco de dados só quando não tiver outra cadastrada.

Vamos englobar o optionsBuilder em um if para verificar !optionsBuilder.IsConfigured. Isto é, quando ele não estiver configurado, vamos utilizar o optionsBuilder que tínhamos como padrão.

JornadaMilhasContext.cs:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
        if(!optionsBuilder.IsConfigured)
        {
                optionsBuilder
                        .UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=JornadaMilhasTest;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False");
        }
}

Estabelecemos uma condição para a utilização da conexão.

Voltamos novamente para classe de teste. No vídeo anterior, trocamos o nome do banco na conexão para poder verificar o comportamento. Agora podemos tirar esse Test do JornadaMilhas no Initial Catalog para voltar para ao banco que queríamos utilizar.

OfertaViagemDalAdicionar.cs:

var options = new DbContextOptionsBuilder<JornadaMilhasContext>()
        .UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=JornadaMilhas;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False")
        .Options;

// código omitido…

Por fim, precisamos ir para a classe de GerenciadorDeOfertas, onde também temos uma referência ao DAL. Então, precisamos passar o contexto.

Em ofertaViagemDAL, passaremos passar o new JornadaMilhasContext() como o contexto do GerenciadorDeOfertas.

GerenciadorDeOfertas.cs:

public class GerenciadorDeOfertas
{
    private List<OfertaViagem> ofertaViagem = new List<OfertaViagem>();
    OfertaViagemDAL ofertaViagemDAL = new OfertaViagemDAL(new JornadaMilhasContext());

    // código omitido…
}

No painel do gerenciador de testes na lateral direita, vamos executar todos os testes.

Todos os testes passaram porque os testes estão pegando a informação de conexão com o banco nos dados que colocamos no arrange, no cenário do nosso teste.

Independentemente da situação do banco original, temos o nosso banco de testes com o controle total. Conseguimos garantir que os resultados dos testes serão os mesmos.

Conceituando baixo acoplamento

O que fizemos foi isolar a conexão do banco de dados na classe de teste. Com isso, conseguimos criar teste de integração com baixo acoplamento, que é justamente essa independência entre os componentes e os componentes externos que estamos utilizando para poder testar.

Dessa forma, garantimos maior controle e também garantir que os nossos testes terão o mesmo resultado independentemente da situação original do componente externo.

No próximo vídeo, vamos analisar o que fizemos até agora e verificar como podemos melhorar esse controle. Até lá!

Sobre o curso Testes em .NET: testando integração com banco de dados

O curso Testes em .NET: testando integração com banco de dados possui 79 minutos de vídeos, em um total de 44 atividades. Gostou? Conheça nossos outros cursos de .NET em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda .NET acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas