Compartilhar via


Passo a passo: criar e executar testes de unidade para código gerenciado

Este artigo explica como criar, executar e personalizar uma série de testes de unidade usando a estrutura de teste de unidade da Microsoft para código gerenciado e o Visual Studio Test Explorer. Você começa com um projeto em C# que está em desenvolvimento, cria testes que exercem seu código, executam os testes e examinam os resultados. Em seguida, você altera o código do projeto e executa os testes novamente. Se você quiser uma visão geral conceitual dessas tarefas antes de passar por essas etapas, consulte noções básicas de teste de unidade. Se você quiser gerar testes automaticamente a partir do código existente, consulte Criar esboços de métodos de teste de unidade a partir do código.

Criar um projeto para testar

  1. Abra o Visual Studio.

  2. Na janela inicial, escolha Criar um novo projeto.

  3. Pesquise e selecione o modelo de projeto do Aplicativo de Console em C# para .NET e clique em Avançar.

    Nota

    Se você não encontrar o modelo Console App, poderá instalá-lo na janela Criar um novo projeto. Na mensagem Não encontrou o que precisa?, escolha o link Instalar mais ferramentas e recursos. Em seguida, no Visual Studio Installer, escolha a carga de trabalho Desenvolvimento de área de trabalho do .NET.

  4. Dê ao projeto o nome Bank e clique em Avançar.

    Escolha a estrutura de destino recomendada ou o .NET 8 e, em seguida, escolha Criar.

    O projeto do Banco é criado e exibido no Gerenciador de Soluções com o arquivo Program.cs aberto no editor de código.

    Nota

    Se Program.cs não estiver aberto no editor, clique duas vezes no arquivo Program.cs no Gerenciador de Soluções para abri-lo.

  5. Substitua o conteúdo de Program.cs pelo seguinte código C# que define uma classe, BankAccount:

    using System;
    
    namespace BankAccountNS
    {
        /// <summary>
        /// Bank account demo class.
        /// </summary>
        public class BankAccount
        {
            private readonly string m_customerName;
            private double m_balance;
    
            private BankAccount() { }
    
            public BankAccount(string customerName, double balance)
            {
                m_customerName = customerName;
                m_balance = balance;
            }
    
            public string CustomerName
            {
                get { return m_customerName; }
            }
    
            public double Balance
            {
                get { return m_balance; }
            }
    
            public void Debit(double amount)
            {
                if (amount > m_balance)
                {
                    throw new ArgumentOutOfRangeException("amount");
                }
    
                if (amount < 0)
                {
                    throw new ArgumentOutOfRangeException("amount");
                }
    
                m_balance += amount; // intentionally incorrect code
            }
    
            public void Credit(double amount)
            {
                if (amount < 0)
                {
                    throw new ArgumentOutOfRangeException("amount");
                }
    
                m_balance += amount;
            }
    
            public static void Main()
            {
                BankAccount ba = new BankAccount("Mr. Bryan Walton", 11.99);
    
                ba.Credit(5.77);
                ba.Debit(11.22);
                Console.WriteLine("Current balance is ${0}", ba.Balance);
            }
        }
    }
    
  6. Renomeie o arquivo como BankAccount.cs clicando com o botão direito do mouse e escolha Renomear na Gerenciador de Soluções.

  7. No menu Build, clique em Build Solution (ou pressione Ctrl + SHIFT + B).

Agora você tem um projeto com métodos que você pode testar. Neste artigo, os testes se concentram no método Debit. O método Debit é chamado quando o dinheiro é retirado de uma conta.

Criar um projeto de teste de unidade

  1. No menu Arquivo, selecione Adicionar>Novo Projeto.

    Dica

    Você também pode clicar com o botão direito do mouse na solução no Gerenciador de Soluções e escolher Adicionar>Novo Projeto.

  2. Digite test na caixa de pesquisa, selecione C# como o idioma e, em seguida, selecione o Projeto de teste do MSTest em C# para o modelo do .NET e clique em Avançar.

    Nota

    No Visual Studio 2019 versão 16.9, o modelo de projeto MSTest é Projeto de Teste de Unidade.

  3. Nomeie o projeto como BankTests e clique em Avançar.

  4. Escolha a estrutura de destino recomendada ou o .NET 8, em seguida, clique em Criar.

    A partir do Visual Studio 2022 versão 17.10, você também pode selecionar um executor de teste. Para o executor de testes, você pode escolher VSTest ou MSTest. Para obter mais informações sobre a diferença entre os executores de teste, confira Comparação entre Microsoft.Testing.Platform e VSTest.

    O projeto BankTests é adicionado à solução Bank.

  5. No projeto BankTests, adicione uma referência ao projeto Bank.

    No Gerenciador de Soluções, selecione Dependências no projeto BankTests e, em seguida, escolha Adicionar Referência (ou Adicionar referência de projeto) no menu de clique com o botão direito do mouse.

  6. Na caixa de diálogo Gerenciador de Referências, expanda Projetos, selecione Solução e, em seguida, marque o item Banco.

  7. Escolha OK.

Criar a classe de teste

Crie uma classe de teste para verificar a classe BankAccount. Você pode usar o arquivo UnitTest1.cs que foi gerado pelo modelo de projeto, mas fornecer ao arquivo e à classe nomes mais descritivos.

Renomear um arquivo e uma classe

  1. Para renomear o arquivo, em do Gerenciador de Soluções, selecione o arquivo UnitTest1.cs no projeto BankTests. No menu de clique com o botão direito do mouse, escolha Renomear (ou pressione F2), e renomeie o arquivo para BankAccountTests.cs.

  2. Para renomear a classe, posicione o cursor no UnitTest1 no editor de código, clique com o botão direito do mouse e escolha Renomear (ou pressione F2). Digite BankAccountTests e, em seguida, pressione Enter.

O arquivo BankAccountTests.cs agora contém o seguinte código:

// The 'using' statement for Test Tools is in GlobalUsings.cs
// using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BankTests
{
    [TestClass]
    public class BankAccountTests
    {
        [TestMethod]
        public void TestMethod1()
        {
        }
    }
}

Adicionar uma instrução using

Adicione uma instrução using à classe de teste para permitir chamadas ao projeto em teste sem usar nomes totalmente qualificados. Na parte superior do arquivo de classe, adicione:

using BankAccountNS;

Requisitos de classe de teste

Os requisitos mínimos para uma classe de teste são:

  • O atributo [TestClass] é necessário em qualquer classe que contenha métodos de teste de unidade que você deseja executar no Gerenciador de Testes.

  • Cada método de teste que você deseja reconhecer o Gerenciador de Testes deve ter o atributo [TestMethod].

Você pode ter outras classes em um projeto de teste de unidade que não têm o atributo [TestClass] e pode ter outros métodos em classes de teste que não têm o atributo [TestMethod]. Você pode chamar essas classes e métodos dos seus métodos de teste.

Criar o primeiro método de teste

Neste procedimento, você escreve métodos de teste de unidade para verificar o comportamento do método Debit da classe BankAccount.

Há pelo menos três comportamentos que precisam ser verificados:

  • O método gerará um ArgumentOutOfRangeException se o valor do débito for maior que o saldo.

  • O método gerará um ArgumentOutOfRangeException se o valor do débito for menor que zero.

  • Se o valor do débito for válido, o método subtrairá o valor do débito do saldo da conta.

Dica

Você pode excluir o método de TestMethod1 padrão, pois não o usará neste passo a passo.

Para criar um método de teste

O primeiro teste verifica se um valor válido (ou seja, um menor que o saldo da conta e maior que zero) retira o valor correto da conta. Adicione o seguinte método à classe BankAccountTests:

[TestMethod]
public void Debit_WithValidAmount_UpdatesBalance()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = 4.55;
    double expected = 7.44;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    account.Debit(debitAmount);

    // Assert
    double actual = account.Balance;
    Assert.AreEqual(expected, actual, 0.001, "Account not debited correctly");
}

O método é simples: ele configura um novo objeto BankAccount com um saldo inicial e, em seguida, retira um valor válido. Ele usa o método Assert.AreEqual para verificar se o saldo final é o esperado. Métodos como Assert.AreEqual, Assert.IsTruee outros são frequentemente usados em testes de unidade. Para obter mais informações conceituais sobre como escrever um teste de unidade, confira Escrever seus testes.

Requisitos do método de teste

Um método de teste deve atender aos seguintes requisitos:

  • Está decorado com o atributo [TestMethod].

  • Ele retorna void.

  • Ele não pode ter parâmetros.

Compilar e executar o teste

  1. No menu Criar, escolha Criar Solução (ou pressione Ctrl + SHIFT + B).

  2. Se o Gerenciador de Testes não estiver aberto, abra-o escolhendo Teste >Gerenciador de Testes (ou Teste >Windows>Gerenciador de Testes) na barra de menus superior (ou pressione Ctrl + E, T ).

  3. Escolha Executar Tudo para executar o teste (ou pressione Ctrl + R, V).

    Enquanto o teste está em execução, a barra de status na parte superior da janela do Gerenciador de Testes é animada. No final da execução do teste, a barra ficará verde se todos os métodos de teste forem aprovados ou vermelhos se algum dos testes falhar.

    Nesse caso, o teste falha.

  4. Selecione o método no Gerenciador de Testes para exibir os detalhes na parte inferior da janela.

Corrigir seu código e executar novamente seus testes

O resultado do teste contém uma mensagem que descreve a falha. Talvez seja necessário ir mais a fundo para ver esta mensagem. Para o método AreEqual, a mensagem exibe o que era esperado e o que realmente foi recebido. Você esperava que o saldo diminuísse, mas, em vez disso, aumentou pelo valor do saque.

O teste de unidade revelou um bug: o valor do saque foi adicionado ao saldo da conta quando deveria ser subtraído.

Corrigir o bug

Para corrigir o erro, no arquivo BankAccount.cs, substitua a linha:

m_balance += amount;

por:

m_balance -= amount;

Executar novamente o teste

Em Explorador de Testes, escolha Executar Tudo para executar novamente o teste (ou pressione Ctrl + R, V). A barra vermelha/verde fica verde para indicar que o teste foi aprovado.

Test Explorer no Visual Studio 2019 mostrando o teste aprovado

Test Explorer no Visual Studio 2019 mostrando o teste aprovado

Usar testes de unidade para melhorar seu código

Esta seção descreve como um processo iterativo de análise, desenvolvimento de teste de unidade e refatoração pode ajudá-lo a tornar seu código de produção mais robusto e eficaz.

Analisar os problemas

Você criou um método de teste para confirmar se um valor válido foi deduzido corretamente no método Debit. Agora, verifique se o método gera uma ArgumentOutOfRangeException se o valor do débito é:

  • maior que o saldo ou
  • menor que zero.

Criar e executar novos métodos de teste

Crie um método de teste para verificar o comportamento correto quando o valor do débito for menor que zero:

[TestMethod]
public void Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = -100.00;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act and assert
    Assert.ThrowsException<System.ArgumentOutOfRangeException>(() => account.Debit(debitAmount));
}

Use o método ThrowsException para afirmar que a exceção correta foi gerada. Esse método faz com que o teste falhe, a menos que um ArgumentOutOfRangeException seja gerado. Se você modificar temporariamente o método em teste para gerar uma ApplicationException mais genérica quando o valor do débito for menor que zero, o teste se comportará corretamente, ou seja, ele falhará.

Para testar o caso quando o valor retirado for maior que o saldo, execute as seguintes etapas:

  1. Crie um novo método de teste chamado Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange.

  2. Copiar o corpo do método de Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange para o novo método.

  3. Defina o debitAmount como um número maior que o saldo.

Execute os dois testes e verifique se eles são aprovados.

Continuar a análise

O método que está sendo testado pode ser aprimorado ainda mais. Com a implementação atual, não temos como saber qual condição (amount > m_balance ou amount < 0) levou à exceção gerada durante o teste. Nós sabemos apenas que um ArgumentOutOfRangeException foi lançado em algum lugar no método. Seria melhor se pudéssemos dizer qual condição em BankAccount.Debit fazia com que a exceção fosse lançada (amount > m_balance ou amount < 0) para que possamos ter certeza de que nosso método está verificando corretamente seus argumentos.

Examine o método que está sendo testado (BankAccount.Debit) novamente e observe que ambas as instruções condicionais usam um construtor ArgumentOutOfRangeException que usa apenas o nome do argumento como um parâmetro:

throw new ArgumentOutOfRangeException("amount");

Há um construtor que você pode usar que relata informações muito mais avançadas: ArgumentOutOfRangeException(String, Object, String) inclui o nome do argumento, o valor do argumento e uma mensagem definida pelo usuário. Você pode refatorar o método em teste para usar esse construtor. Melhor ainda, use membros de tipo disponíveis publicamente para especificar os erros.

Refatorar o código em teste

Primeiro, defina duas constantes para as mensagens de erro no escopo da classe. Coloque as definições na classe em teste, BankAccount:

public const string DebitAmountExceedsBalanceMessage = "Debit amount exceeds balance";
public const string DebitAmountLessThanZeroMessage = "Debit amount is less than zero";

Em seguida, modifique as duas instruções condicionais no método Debit:

if (amount > m_balance)
{
    throw new System.ArgumentOutOfRangeException("amount", amount, DebitAmountExceedsBalanceMessage);
}

if (amount < 0)
{
    throw new System.ArgumentOutOfRangeException("amount", amount, DebitAmountLessThanZeroMessage);
}

Refatorar os métodos de teste

Refatore os métodos de teste removendo a chamada a Assert.ThrowsException. Encapsule a chamada a Debit() em um bloco try/catch, capture a exceção específica que é esperada e verifique a mensagem associada. O método Microsoft.VisualStudio.TestTools.UnitTesting.StringAssert.Contains fornece a capacidade de comparar duas cadeias de caracteres.

Agora, o Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange pode ter esta aparência:

[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = 20.0;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    try
    {
        account.Debit(debitAmount);
    }
    catch (System.ArgumentOutOfRangeException e)
    {
        // Assert
        StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage);
    }
}

Retestar, reescrever e reanalisar

Atualmente, o método de teste não lida com todas as situações que deveria. Se o método em teste, o método Debit, não lançar um ArgumentOutOfRangeException quando o debitAmount for maior que o saldo (ou menor que zero), o método de teste será aprovado. Esse cenário não é bom porque você deseja que o método de teste falhe se nenhuma exceção for lançada.

Esse resultado é um bug no método de teste. Para resolver o problema, adicione uma declaração Assert.Fail no final do método de teste para lidar com o caso em que nenhuma exceção é gerada.

Uma nova execução do teste mostra que agora o teste falha se a exceção correta é capturada. O bloco catch captura a exceção, mas o método continua a ser executado e falha na nova declaração Assert.Fail. Para resolver esse problema, adicione uma instrução return após o StringAssert no bloco catch. Executar novamente o teste confirma que você corrigiu esse problema. A versão final do Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange tem esta aparência:

[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = 20.0;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    try
    {
        account.Debit(debitAmount);
    }
    catch (System.ArgumentOutOfRangeException e)
    {
        // Assert
        StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage);
        return;
    }

    Assert.Fail("The expected exception was not thrown.");
}

Conclusão

As melhorias no código de teste levaram a métodos de teste mais robustos e informativos. Mas, mais importante, eles também melhoraram o código em teste.

Dica

Este passo a passo usa a estrutura de teste de unidade da Microsoft para código gerenciado. O Gerenciador de Testes também pode executar testes em estruturas de teste de unidade de terceiros que têm adaptadores para o Gerenciador de Testes. Para saber mais, consulte Instalar estruturas de teste de unidade de terceiros.

Para obter informações sobre como executar testes de uma linha de comando, consulte VSTest.Console.exe opções de linha de comando.