Testes Unitários com C# e xUnit – Parte 2: Escrevendo seus primeiros testes

Olá, pessoal. Tudo bem?

Sigo com a escrita de artigos sobre Testes Unitários aqui. Na parte 1 falei sobre O que são, para que servem e quais os obstáculos.

Neste artigo, abordarei o conteúdo de maneira mais prática, e vou propor uma atividade ao final! O código utilizado nesse artigo está hospedado neste repositório.


Apresentação do xUnit

O xUnit é um framework para testes unitários que é flexível e completo, além de ser parte do .NET Foundation e ter sido escrito pelo criador da v2 do NUnit.

Ele oferece diversos atributos e métodos que permitem e facilitam a escrita e verificação em testes unitários. Alguns serão mostrados nos códigos a seguir.


Criando um projeto xUnit

Visual Studio 2019

Existe um template para isso! Abra o Visual Studio, clique em Criar um Projeto. Após isso, selecione C# como a linguagem, e Teste como tipo de projeto. Busque na lista por Projeto de Teste do xUnit (.NET Core). Imagem a seguir ilustra a opção.

.NET Core CLI

A linha de comando do .NET Core facilita bastante a criação de projetos, não apenas de teste! Abra o terminal, navegue para o local que deverá conter a pasta do seu projeto e execute o seguinte código.

dotnet new xunit -o <nome_do_projeto>

Cenário

O cenário é de uma aplicação de um banco, que é responsável por coordenar operações financeiras. Para facilitar o entendimento e simplificar o projeto, apenas uma classe é responsável pelas operações, além de que apenas a camada de dominio é testada.

Nessa situação, apresento quatro operações passíveis de cobertura de testes:

  • Transferência de Conta A para Conta B com saldo e limite suficientes devem resultar em sucesso.
  • Transferência de Conta A para Conta B com saldo suficiente mas com limite insuficiente deve falhar.
  • Transferência de Conta A para Conta B com limite suficiente porém com saldo insuficiente deve falhar.
  • Transferência de Conta A para Conta B com saldo e limite insuficientes deve falhar.

Para essas operações, diversas checagens podem ser feitas:

  • Em situações de sucesso, deve-se verificar que o valor da operação foi debitado da conta de origem, e crédito na conta de destino, além do limite ter sido ajustado de acordo com isso.
  • Em situações de falha, deve-se verificar que o saldo em ambas as contas não foi alterado, assim como os limites.

Tópicos importantes antes de escrever o teste unitário

Estrutura de uma classe de teste xUnit

Uma classe de teste xUnit não precisa de nenhum atributo específico anexado a ela. Porém, seus métodos precisarão ter o atributo Fact, do namespace Xunit. Quando o executador de testes for rodado, ele buscará por métodos com esse atributo e irá adicioná-los ao suíte de testes.

Padronizando o nome da classe e do método

Para evitar que uma classe de testes unitários se refira a diversos métodos, logo se extendendo demais e dificultando a busca e manutenção de testes, recomendo criar um arquivo por método a ser testado.

É uma preferência minha. Imagina que uma classe de serviço tenha 5 métodos, e cada método tenha 4 casos. Isso dará um total de 20 métodos de teste na classe, um número bem grande.

Normalmente crio a classe com o nome do serviço, do método e adiciono “Tests” ao final. Logo, a nossa classe de teste se chamará OperacaoFinanceiraServiceTransferirTests.

Para o nome do método gosto de utilizar o padrão Given_When_Then.

Given: Traduzido como “Dado”, explica as condições anteriores ao teste. Por exemplo, nos nossos casos:

  • Para o caso da conta de origem ter limite e saldo suficiente, pode ser “ContaOrigemTemLimiteESaldoSuficiente”.
  • Para o caso da conta ter limite, mas saldo insuficiente, pode ser “ContaOrigemTemLimiteMasSaldoInsuficiente”.

When: Traduzido como “Quando”, apresenta a chamada do método e quaisquer parâmetros passados. Por exemplo, pode ser “ChamadoComContasEValoresValidos”, “ChamadoComContaDestinoNull” ou “ChamadoComValorZero”.

Then: Traduzido como “Então”, apresenta o resultado. Em nossas situações, por exemplo, poderia ser:

  • Para o caso da conta de origem ter limite e saldo suficiente, pode ser “RetornarSucessoEDebitarDosSaldosELImiteCorretamente”.
  • Para o caso da conta ter limite, mas saldo insuficiente, pode ser “RetornarFalhaENaoAlterarSaldosELimites”.

Um exemplo completo de nome de método seria ContaOrigemTemLimiteESaldoSuficiente_ChamadoComContasEValoresValidos_RetornarSucessoEDebitarDosSaldosELimiteCorretamente.

Acha grande? Bom, o título do método está bem claro, acredito, e se a classe de testes fosse utilizada para mais de um método a ser testado, o nome do método deveria ser adicionado ao nome do método de teste. Isso tornaria o nome ainda maior e deixaria um “ruído” de redundância ao percorrer a classe de testes, na minha opinião.

Padronizando a implementação do teste unitário

Um padrão comumente utilizado (e que gosto) para se implementarem testes unitários é o Arrange-Act-Assert (conhecido também como AAA).

  • Arrange: São realizados os preparativos para a ação e resultado a serem testados. Inicializações, alterações de estado e definições de Mocks (no próximo artigo discutirei sobre eles e o pacote Moq) são feitas aqui.
  • Act: A ação a ser realizada é feita nessa parte, além do resultado ser obtido (ou não, caso seja um método sem retorno).
  • Assert: Verificações são feitas nessa etapa, se constituindo concretamente nos parâmetros de aprovação de um teste unitário. Comparações por igualdades de valores obtidos x esperados, verificação de chamadas de métodos e/ou exceções são alguns exemplos do que pode ser feito nessa etapa.

Escrevendo seu primeiro teste

Para facilitar o seguimento dos códigos a seguir, recomendo clonar o projeto que criei aqui.

A classe principal (e seus métodos) a ser testada é a classe OperacaoFinanceiraService. O método que está codificado é Transferencia, que recebe como parâmetros uma entidade ContaCorrente de origem, outra entidade ContaCorrente como conta de destino e o valor da operação.

Primeiro caso: Contas válidas para transação, com a conta origem tendo limite e saldo suficiente

Começamos o teste definindo os valores esperados. Estes são armazenados em variáveis constantes. Após isso, seguimos com o padrão AAA explicado anteriormente.

São utilizadas classes Factory para evitar instanciar repetidamente objetos em cada um dos testes, deixando o código mais limpo.

Após os preparativos e a chamada ao método que está sob teste, são realizados os Assert baseados nos valores esperados.

Os métodos oferecidos pelo xUnit e utilizados no código acima, Assert.True e Assert.Equal, são os que provavelmente você mais vai utilizar. O primeiro recebe um valor booleano e checa se é true (a versão Assert.False existe também, para a verificação oposta). O Assert.Equal recebe dois parâmetros, sendo o primeiro o valor esperado, e em seguida o valor a ser comparado.

Usando o Visual Studio 2019, os testes unitários podem ser executados através do comando CTRL+R, A ou através do Menu Testes > Executar Todos Testes.

Já no Visual Studio Code, navega-se para a pasta raiz do projeto de Testes, e executa-se o comando dotnet test.

Executamos o teste e…

Sucesso! Verificaremos os valores de saldos e limites em cada uma das contas de acordo com os requisitos descrito em Cenário. O resultado da operação foi um valor booleano true.

Segundo caso: Conta de origem inválida para transação, tendo limite porém com saldo insuficiente

Novamente definimos os valores esperados e dessa vez utilizamos um valor de transação acima do saldo inicial de uma conta corrente. Após isso, inicializamos os objetos necessários na etapa de Arrange.

A suíte de testes agora reconhece o novo teste unitário, e ao executar eles…

Sucesso em verificar a falha! haha Verifica-se que os saldos são iguais aos iniciais, e que o resultado da operação retornou um valor booleano igual a false.


Atividade proposta

Que tal criar um fork do projeto no GitHub e implementar os testes unitários que cubram as operações restantes descritas em Cenário para praticar? Aproveite para compartilhar os resultados na sessão de comentários!


Quer alavancar sua carreira como Desenvolvedor(a) .NET?

Opa, aqui é o Luis Felipe (LuisDev), criador do blog LuisDev.

Além de Desenvolvedor .NET Sênior, eu sou instrutor de mais de 700 alunos e também tenho dezenas de mentorados.

Conheça o com mais de 800 video-aulas sobre C# e desenvolvimento de APIs com ASP NET Core, Microsserviços com ASP NET Core, Arquitetura de Software, Computação em Nuvem, SQL, HTML, CSS e JavaScript, JavaScript Intermediário, TypeScript, Desenvolvimento Front-End com Angular, e Desenvolvimento Front-end com React. Diversos mini-cursos disponíveis aos alunos e atualizações gratuitas.

Suporte dedicado, e comunidade de centenas de alunos.

Completo e online, destinado a profissionais que querem dar seu próximo passo em sua carreira como desenvolvedores .NET.

Clique aqui para ter mais informações e garantir sua vaga


Conclusão

Foi visto neste artigo como criar um projeto de testes unitários com xUnit, além de ter sido apresentados padrões para a escrita deles e imagens de sua execução.

Novamente, o código mostrado está no repositório criado para essa série de artigos que pode ser acessado aqui.

No próximo artigo me aprofundarei em mais casos de testes (envolvendo exceções, por exemplo), além de apresentar um pacote bastante importante e utilizado com testes unitários no geral, o Moq. Tópicos importantes para qualquer desenvolvedor, como o respeito ao príncipio de Inversão de Dependência do SOLID, será abordado no contexto de escrita de testes unitários e utilização de Mocks.

É isso, até o próximo artigo!

Se achou o artigo interessante, te convido a comentar, e/ou compartilhar!