Exercício – Adicionar testes de unidade a seu aplicativo

Concluído

Nesta unidade, adicionaremos testes de unidade ao build automatizado que criamos com o Microsoft Azure Pipelines. Bugs de regressão estão se infiltrando no código da sua equipe e prejudicando a funcionalidade de filtragem do placar de líderes. Especificamente, o modo de jogo incorreto continua aparecendo.

A imagem a seguir ilustra o problema. Quando um usuário seleciona "Via Láctea" para mostrar apenas as pontuações daquele mapa do jogo, ele obtém resultados de outros mapas do jogo, como Andrômeda.

A screenshot of the leaderboard showing incorrect results: Andromeda galaxy scores show in the Milky Way galaxy listing.

A equipe quer detectar o erro antes de chegar aos testadores. Testes de unidade são uma ótima maneira de testar automaticamente para determinar se há bugs de regressão.

Adicionar os testes de unidade neste momento do processo dará à equipe uma vantagem ao melhorar o aplicativo Web Space Game. O aplicativo usa um banco de dados de documentos para armazenar perfis de jogador e pontuações altas. Agora mesmo, ele usa dados de teste locais. Posteriormente, planejam conectar o aplicativo a um banco de dados ativo.

Há várias estruturas de teste de unidade disponíveis para aplicativos C#. Usaremos o NUnit porque ele é popular na comunidade.

Aqui está o teste de unidade com o qual você está trabalhando:

[TestCase("Milky Way")]
[TestCase("Andromeda")]
[TestCase("Pinwheel")]
[TestCase("NGC 1300")]
[TestCase("Messier 82")]
public void FetchOnlyRequestedGameRegion(string gameRegion)
{
    const int PAGE = 0; // take the first page of results
    const int MAX_RESULTS = 10; // sample up to 10 results

    // Form the query predicate.
    // This expression selects all scores for the provided game region.
    Expression<Func<Score, bool>> queryPredicate = score => (score.GameRegion == gameRegion);

    // Fetch the scores.
    Task<IEnumerable<Score>> scoresTask = _scoreRepository.GetItemsAsync(
        queryPredicate, // the predicate defined above
        score => 1, // we don't care about the order
        PAGE,
        MAX_RESULTS
    );
    IEnumerable<Score> scores = scoresTask.Result;

    // Verify that each score's game region matches the provided game region.
    Assert.That(scores, Is.All.Matches<Score>(score => score.GameRegion == gameRegion));
}

Você pode filtrar o placar de líderes por qualquer combinação de tipo de jogo e mapa de jogo.

Esse teste consulta o placar de líderes quanto a pontuações altas e verifica se cada resultado corresponde ao mapa de jogos fornecido.

Em um método teste NUnit, TestCase fornece dados embutidos para usar esse método de teste. Aqui, NUnit chama o método de teste de unidade FetchOnlyRequestedGameRegion, da seguinte maneira:

FetchOnlyRequestedGameRegion("Milky Way");
FetchOnlyRequestedGameRegion("Andromeda");
FetchOnlyRequestedGameRegion("Pinwheel");
FetchOnlyRequestedGameRegion("NGC 1300");
FetchOnlyRequestedGameRegion("Messier 82");

Observe a chamada para o método Assert.That no final do teste. Uma asserção é uma condição ou instrução que você declara ser verdadeira. Se a condição acabar se mostrando falsa, isso poderá indicar um bug em seu código. NUnit executa cada método de teste usando os dados embutidos que você especifica e registra o resultado como um teste aprovado ou reprovado.

Muitas estruturas de teste de unidade fornecem métodos de verificação que se assemelhem à linguagem natural. Esses métodos ajudam a facilitar a leitura dos testes e a mapear os testes para os requisitos do aplicativo.

Considere a asserção feita neste exemplo:

Assert.That(scores, Is.All.Matches<Score>(score => score.GameRegion == gameRegion));

Você pode ler essa linha como:

Declare que a região do jogo de cada pontuação retornada corresponde à região do jogo fornecida.

Este é o processo a ser seguido:

  1. Busque um branch no repositório GitHub que contém os testes de unidade.
  2. Execute os testes localmente para verificar se eles são aprovados.
  3. Adicione tarefas à sua configuração de pipeline para executar os testes e coletar os resultados.
  4. Enviar por push o branch para seu repositório do GitHub.
  5. Assista a seu projeto do Azure Pipelines compilar automaticamente o aplicativo e executar os testes.

Buscar o branch do GitHub

Aqui, você buscará o branch unit-tests no GitHub e fará o checkout, ou seja, mudará para esse branch.

Este branch contém o projeto Space Game com o qual você trabalhou nos módulos anteriores e uma configuração do Azure Pipelines com a qual começar.

  1. No Visual Studio Code, abra o terminal integrado.

  2. Execute os comandos git a seguir para buscar um branch chamado unit-tests do repositório da Microsoft e alternar para ele.

    git fetch upstream unit-tests
    git checkout -B unit-tests upstream/unit-tests
    

    O formato desse comando permite que você obtenha o código inicial do repositório GitHub da Microsoft, conhecido como upstream. Em breve, você efetuará push desse branch para seu repositório GitHub, conhecido como origin.

  3. Como uma etapa opcional, abra o arquivo azure-pipelines.yml no Visual Studio Code e familiarize-se com a configuração inicial. A configuração é semelhante à básica criada no módulo Criar um pipeline de build com o Azure Pipelines. Ela compila apenas a configuração de Versão do aplicativo.

Executar os testes localmente

É uma boa ideia executar todos os testes localmente antes de enviá-los para o pipeline. Você fará isso aqui.

  1. No Visual Studio Code, abra o terminal integrado.

  2. Execute dotnet build para compilar cada projeto na solução.

    dotnet build --configuration Release
    
  3. Execute o seguinte comando dotnet test para executar os testes de unidade:

    dotnet test --configuration Release --no-build
    

    O sinalizador --no-build especifica para não compilar o projeto antes de executá-lo. Você não precisa compilar o projeto porque ele é compilado na etapa anterior.

    Você deverá ver que todos os cinco testes foram aprovados.

    Starting test execution, please wait...
    A total of 1 test files matched the specified pattern.
    
    Passed!  - Failed:     0, Passed:     5, Skipped:     0, Total:     5, Duration: 57 ms
    

    Neste exemplo, os testes levaram menos de um segundo para serem executados.

    Observe que havia um total de cinco testes. Embora tenhamos definido apenas um método de teste, FetchOnlyRequestedGameRegion, esse teste é executado cinco vezes, uma vez para cada mapa do jogo especificado nos dados embutidos em TestCase.

  4. Execute os testes uma segunda vez. Desta vez, forneça a opção --logger para gravar os resultados em um arquivo de log.

    dotnet test Tailspin.SpaceGame.Web.Tests --configuration Release --no-build --logger trx
    

    Você vê na saída que um arquivo TRX é criado no diretório TestResults.

    Um arquivo TRX é um documento XML que contém os resultados de uma execução de teste. É um formato popular para resultados de teste, pois o Visual Studio e outras ferramentas podem ajudar a visualizar os resultados.

    Posteriormente, você verá como o Azure Pipelines pode ajudá-lo a visualizar e acompanhar os resultados do teste enquanto eles são executados por meio do pipeline.

    Observação

    Os arquivos TRX não devem ser incluídos no controle do código-fonte. Um arquivo .gitignore permite que você especifique quais arquivos temporários e outros deseja que o Git ignore. O arquivo .gitignore do projeto já está configurado para ignorar o diretório TestResults.

  5. Como uma etapa opcional, no Visual Studio Code, abra o arquivo DocumentDBRepository_GetItemsAsyncShould.cs localizado na pasta Tailspin.SpaceGame.Web.Tests e examine o código de teste. Mesmo que você não esteja interessado em compilar aplicativos .NET especificamente, talvez considere o código de teste útil, pois se parece com o código que você pode ver em outras estruturas de teste de unidade.

Adicionar tarefas à sua configuração de pipeline

Aqui, você irá configurar o pipeline de build para executar seus testes de unidade e coletar os resultados.

  1. No Visual Studio Code, modifique azure-pipelines.yml desta forma:

    trigger:
    - '*'
    
    pool:
      vmImage: 'ubuntu-20.04'
      demands:
      - npm
    
    variables:
      buildConfiguration: 'Release'
      wwwrootDir: 'Tailspin.SpaceGame.Web/wwwroot'
      dotnetSdkVersion: '6.x'
    
    steps:
    - task: UseDotNet@2
      displayName: 'Use .NET SDK $(dotnetSdkVersion)'
      inputs:
        version: '$(dotnetSdkVersion)'
    
    - task: Npm@1
      displayName: 'Run npm install'
      inputs:
        verbose: false
    
    - script: './node_modules/.bin/node-sass $(wwwrootDir) --output $(wwwrootDir)'
      displayName: 'Compile Sass assets'
    
    - task: gulp@1
      displayName: 'Run gulp tasks'
    
    - script: 'echo "$(Build.DefinitionName), $(Build.BuildId), $(Build.BuildNumber)" > buildinfo.txt'
      displayName: 'Write build info'
      workingDirectory: $(wwwrootDir)
    
    - task: DotNetCoreCLI@2
      displayName: 'Restore project dependencies'
      inputs:
        command: 'restore'
        projects: '**/*.csproj'
    
    - task: DotNetCoreCLI@2
      displayName: 'Build the project - $(buildConfiguration)'
      inputs:
        command: 'build'
        arguments: '--no-restore --configuration $(buildConfiguration)'
        projects: '**/*.csproj'
    
    - task: DotNetCoreCLI@2
      displayName: 'Run unit tests - $(buildConfiguration)'
      inputs:
        command: 'test'
        arguments: '--no-build --configuration $(buildConfiguration)'
        publishTestResults: true
        projects: '**/*.Tests.csproj'
    
    - task: DotNetCoreCLI@2
      displayName: 'Publish the project - $(buildConfiguration)'
      inputs:
        command: 'publish'
        projects: '**/*.csproj'
        publishWebProjects: false
        arguments: '--no-build --configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)/$(buildConfiguration)'
        zipAfterPublish: true
    
    - task: PublishBuildArtifacts@1
      displayName: 'Publish Artifact: drop'
      condition: succeeded()
    

    Esta versão apresenta esta tarefa de build DotNetCoreCLI@2.

    - task: DotNetCoreCLI@2
      displayName: 'Run unit tests - $(buildConfiguration)'
      inputs:
        command: 'test'
        arguments: '--no-build --configuration $(buildConfiguration)'
        publishTestResults: true
        projects: '**/*.Tests.csproj'
    

    Esta tarefa de build executa o comando dotnet test.

    Observe que esta tarefa não especifica o argumento --logger trx, que você usou quando executou os testes manualmente. O argumento publishTestResults adiciona isso para você. Esse argumento informa ao pipeline para gerar o arquivo TRX para um diretório temporário, acessível por meio da variável interna $(Agent.TempDirectory). Ele também publica os resultados da tarefa para o pipeline.

    O argumento projects especifica todos os projetos C# que correspondem a "**/*.Tests.csproj." A parte "**" corresponde a todos os diretórios e "*. Tests. csproj" corresponde a todos os projetos cujo nome de arquivo termine com ".Tests.csproj." A ramificação unit-tests contém apenas um projeto de teste de unidade, Tailspin.SpaceGame.Web.Tests.csproj. Ao especificar um padrão, você pode executar mais projetos de teste sem precisar modificar a configuração de build.

Enviar o branch por push ao GitHub

Aqui, você enviará as alterações por push ao GitHub e verá a execução do pipeline. Lembre-se de que, no momento, você está no branch unit-tests.

  1. No terminal integrado, adicione azure-pipelines.yml ao índice, faça commit das alterações e efetue push do branch para o GitHub.

    git add azure-pipelines.yml
    git commit -m "Run and publish unit tests"
    git push origin unit-tests
    

Assista aos Azure Pipelines executarem os testes

Aqui, você verá os testes serem executados no pipeline e então visualizará os resultados por meio do Microsoft Azure Test Plans. O Azure Test Plans fornece todas as ferramentas de que você precisa para testar com êxito seus aplicativos. Você pode criar e executar planos de teste manuais, gerar testes automatizados e coletar os comentários dos participantes.

  1. No Azure Pipelines, rastreie o build por meio de cada uma das etapas.

    Você verá que a tarefa Executar testes de unidade – Versão executa os testes de unidade como você fez manualmente na linha de comando.

    A screenshot of Azure Pipelines showing console output from running unit tests.

  2. Navegue de volta para o resumo do pipeline.

  3. Vá para a guia Testes.

    Você verá um resumo da execução de teste. Todos os cinco testes foram aprovados.

    A screenshot of Azure Pipelines showing the Tests tab with 5 total tests run and 100 percent passing.

  4. No Azure DevOps, selecione Test Plans e Runs.

    A screenshot of Azure DevOps navigation menu with Test Plans section and Runs tab highlighted.

    Você verá as execuções de testes mais recentes, incluindo aquelas que você acabou de executar.

  5. Clique duas vezes a execução de teste mais recente.

    Você verá um resumo dos resultados.

    A screenshot of Azure DevOps test run results summary showing 5 passed tests.

    Neste exemplo, todos os cinco testes foram aprovados. Se algum teste falhar, você poderá navegar até a tarefa de compilação para obter mais detalhes.

    Você também pode baixar o arquivo TRX para examiná-lo usando o Visual Studio ou outra ferramenta de visualização.

Apesar de ter adicionado apenas um teste, é um bom começo e resolve o problema imediato. Agora, a equipe tem um local para adicionar mais testes e executá-los conforme melhoram o processo.

Mesclar seu branch no principal

Em um cenário do mundo real, se você estiver feliz com os resultados, poderá mesclar o branch unit-tests ao main, mas por questões de brevidade, ignoraremos esse processo por enquanto.