Dia na vida de um desenvolvedor devops: Escreva um novo código para uma história de usuário

Serviços de DevOps do Azure | Azure DevOps Server 2022 - Azure DevOps Server 2019

Visual Studio 2019 | Visual Studio 2022

Este tutorial explica como você e sua equipe podem obter o máximo benefício das versões mais recentes do Controle de Versão do Team Foundation (TFVC) e do Visual Studio para criar seu aplicativo. O tutorial fornece exemplos de como você pode usar o Visual Studio e o TFVC para fazer check-out e atualizar código, suspender o trabalho quando for interrompido, solicitar uma revisão de código, fazer check-in de suas alterações e executar outras tarefas.

Quando uma equipe adota o Visual Studio e o TFVC para gerenciar seu código, eles configuram suas máquinas cliente e servidor, criam uma lista de pendências, planejam uma iteração e concluem outros planejamentos necessários para começar a desenvolver seu aplicativo.

Os desenvolvedores revisam suas listas de pendências para selecionar tarefas nas quais trabalhar. Eles escrevem testes de unidade para o código que planejam desenvolver. Normalmente, eles executam os testes várias vezes em uma hora, escrevendo gradualmente testes mais detalhados e, em seguida, escrevendo o código que os faz passar. Os desenvolvedores geralmente discutem suas interfaces de código com colegas que usarão o método que estão escrevendo.

As ferramentas Visual Studio My Work e Code Review ajudam os desenvolvedores a gerenciar seu trabalho e colaborar com colegas.

Nota

Os recursos Visual Studio My Work e Code Review estão disponíveis com as seguintes edições:

  • Visual Studio 2022: Comunidade do Visual Studio, Visual Studio Professional e Visual Studio Enterprise
  • Visual Studio 2019: Visual Studio Professional e Visual Studio Enterprise

Revisar itens de trabalho e preparar-se para começar o trabalho

A equipe concordou que, durante o sprint atual, você trabalhará em Avaliar o status da fatura, um item de prioridade máxima na lista de pendências do produto. Você decide começar com Implementar funções matemáticas, uma tarefa filho do item de lista de pendências de prioridade máxima.

No Visual Studio Team Explorer, na página Meu Trabalho , arraste essa tarefa da lista Itens de Trabalho Disponíveis para a lista Trabalho em Andamento.

Para rever a sua lista de pendências e preparar tarefas para começar a trabalhar

Captura de ecrã da página O Meu Trabalho.

  1. No Team Explorer, se você ainda não estiver conectado ao projeto no qual deseja trabalhar, conecte-se ao projeto.

  2. Na página inicial, selecione Meu trabalho.

  3. Na página Meu Trabalho, arraste a tarefa da lista Itens de Trabalho Disponíveis para a seção Trabalho em Andamento.

    Também pode selecionar a tarefa na lista Itens de Trabalho Disponíveis e, em seguida, selecionar Iniciar.

Rascunho de plano de trabalho incremental

Você desenvolve código em uma série de pequenas etapas. Cada passo normalmente não leva mais de uma hora e pode levar apenas 10 minutos. Em cada etapa, você escreve um novo teste de unidade e altera o código que está desenvolvendo para que ele passe no novo teste, além dos testes que você já escreveu. Às vezes você escreve o novo teste antes de alterar o código, e às vezes você altera o código antes de escrever o teste. Às vezes você refatora. Ou seja, você apenas melhora o código sem adicionar novos testes. Você nunca altera um teste que é aprovado, a menos que decida que ele não representou corretamente um requisito.

No final de cada pequena etapa, você executa todos os testes de unidade que são relevantes para esta área do código. Você não considera a etapa concluída até que todos os testes sejam aprovados.

Você não faz check-in do código no Servidor de DevOps do Azure até concluir toda a tarefa.

Você pode escrever um plano aproximado para esta sequência de pequenos passos. Você sabe que os detalhes exatos e a ordem dos últimos provavelmente mudarão à medida que você trabalha. Aqui está a lista inicial de etapas para essa tarefa específica:

  1. Crie o stub do método de teste, ou seja, apenas a assinatura do método.
  2. Satisfaça um caso típico específico.
  3. Teste uma ampla gama. Certifique-se de que o código responde corretamente a um grande intervalo de valores.
  4. Exceção em caso negativo. Lide graciosamente com parâmetros incorretos.
  5. Cobertura de código. Certifique-se de que pelo menos 80% do código é exercido pelos testes de unidade.

Alguns desenvolvedores escrevem esse tipo de plano em comentários em seu código de teste. Outros apenas memorizam o seu plano. Pode ser útil escrever sua lista de etapas no campo Descrição do item de trabalho Tarefa. Se tiver de mudar temporariamente para uma tarefa mais urgente, sabe onde encontrar a lista quando puder voltar a ela.

Criar o primeiro teste de unidade

Comece criando um teste de unidade. Comece com o teste de unidade porque você deseja escrever um exemplo de código que usa sua nova classe.

Este é o primeiro teste de unidade para a biblioteca de classes que você está testando, portanto, você cria um novo projeto de teste de unidade.

  1. Selecione Arquivo>Novo Projeto.
  2. Na caixa de diálogo Criar um novo projeto, selecione a seta ao lado de Todos os idiomas e selecione C#, selecione a seta ao lado de Todos os tipos de projeto e escolha Testar e, em seguida, selecione Projeto de teste MSTest.
  3. Selecione Seguinte e selecione Criar.

Captura de tela do Teste de Unidade selecionado na caixa de diálogo Criar um novo projeto.

No editor de códigos, substitua o conteúdo do UnitTest1.cs pelo código a seguir. Nesta etapa, você só quer ilustrar como um dos seus novos métodos será invocado:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Fabrikam.Math.UnitTest
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        // Demonstrates how to call the method.
        public void SignatureTest()
        {
            // Create an instance:
            var math = new Fabrikam.Math.LocalMath();

            // Get a value to calculate:
            double input = 0.0;

            // Call the method:
            double actualResult = math.SquareRoot(input);

            // Use the result:
            Assert.AreEqual(0.0, actualResult);
        }
    }
}

Você escreve o exemplo em um método de teste porque, no momento em que escreve seu código, deseja que o exemplo funcione.

Para criar um projeto e métodos de teste de unidade

Normalmente, você cria um novo projeto de teste para cada projeto que está sendo testado. Se já existir um projeto de teste, basta adicionar novos métodos e classes de teste.

Este tutorial usa o Visual Studio Unit Test Framework, mas você também pode usar estruturas de outros provedores. O Test Explorer funciona igualmente bem com outras estruturas, desde que você instale o adaptador apropriado.

  1. Crie um projeto de teste usando as etapas anteriores. Você pode escolher linguagens como C#, F# e Visual Basic.

  2. Adicione seus testes à classe de teste fornecida. Cada teste unitário é um método.

    • Cada teste de unidade deve ser prefixado pelo TestMethod atributo e o método de teste de unidade não deve ter parâmetros. Você pode usar qualquer nome que desejar para um método de teste de unidade:

      [TestMethod]
      public void SignatureTest()
      {...}
      
      <TestMethod()>
      Public Sub SignatureTest()
      ...
      End Sub
      
    • Cada método de teste deve chamar um método da Assert classe, para indicar se ele passou ou falhou. Normalmente, você verifica se os resultados esperados e reais de uma operação são iguais:

      Assert.AreEqual(expectedResult, actualResult);
      
      Assert.AreEqual(expectedResult, actualResult)
      
    • Seus métodos de teste podem chamar outros métodos comuns que não têm o TestMethod atributo.

    • Pode organizar os seus testes em mais do que uma turma. Cada classe deve ser prefixada TestClass pelo atributo.

      [TestClass]
      public class UnitTest1
      { ... }
      
      <TestClass()>
      Public Class UnitTest1
      ...
      End Class
      

Para obter informações sobre como escrever testes de unidade em C++, consulte Escrevendo testes de unidade para C/C++ com o Microsoft Unit Testing Framework para C++.

Criar um stub para o novo código

Em seguida, crie um projeto de biblioteca de classes para seu novo código. Agora há um projeto para o código em desenvolvimento e um projeto para os testes de unidade. Adicione uma referência de projeto do projeto de teste ao código em desenvolvimento.

Captura de tela do Gerenciador de Soluções com projetos Test e Class.

No novo projeto, você adiciona a nova classe e uma versão mínima do método que pelo menos permitirá que o teste seja compilado com êxito. A maneira mais rápida de fazer isso é gerar um stub de classe e método a partir da invocação no teste.

public double SquareRoot(double p)
{
    throw new NotImplementedException();
}

Para gerar classes e métodos a partir de testes

Primeiro, crie o projeto onde você deseja adicionar a nova classe, a menos que ela já exista.

Para gerar uma classe

  1. Coloque o cursor em um exemplo da classe que você deseja gerar, por exemplo, LocalMathe selecione Ações rápidas e refatoração.
  2. No menu de atalho, escolha Gerar novo tipo.
  3. Na caixa de diálogo Gerar tipo, defina Project como o projeto da biblioteca de classes. Neste exemplo, é Fabrikam.Math.

Para gerar um método

  1. Coloque o cursor em uma chamada para o método, por exemplo, SquareRoote selecione Ações rápidas e refatoração.
  2. No menu de atalho, escolha Gerar método 'SquareRoot'.

Execute o primeiro teste

Crie e execute o teste. O resultado do teste mostra um indicador vermelho de Reprovação e o teste aparece na lista de Testes Reprovados.

Captura de tela do Test Explorer mostrando um teste reprovado.

Faça uma alteração simples no código:

public double SquareRoot(double p)
{
    return 0.0;
}

Execute o teste novamente e ele passa.

Captura de ecrã do Unit Test Explorer com um teste aprovado.

Para executar testes de unidade

Para executar testes de unidade:

  • Selecione Testar>Executar Todos os Testes
  • Ou, se o Gerenciador de Testes estiver aberto, escolha Executar ou Executar Todos os Testes em Exibição.

Captura de ecrã do Explorador de Testes a mostrar o botão Executar Tudo.

Se um teste aparecer em Testes reprovados, abra o teste, por exemplo, clicando duas vezes no nome. O ponto em que o teste falhou é exibido no editor de código.

  • Para ver uma lista completa de testes, escolha Mostrar tudo.

  • Para ver os detalhes de um resultado de teste, selecione o teste no Gerenciador de Testes.

  • Para navegar até o código de um teste, clique duas vezes no teste no Gerenciador de Testes ou escolha Abrir Teste no menu de atalho.

  • Para depurar um teste, abra o menu de atalho para um ou mais testes e escolha Depurar.

  • Para executar testes em segundo plano sempre que criar a solução, selecione a seta ao lado do ícone Configurações e, em seguida, selecione Executar testes após a compilação. Os testes que falharam anteriormente são executados primeiro.

Concordar com a interface

Você pode colaborar com colegas que usarão seu componente compartilhando sua tela. Um colega pode comentar que muitas funções passariam no teste anterior. Explique que este teste foi apenas para se certificar de que o nome e os parâmetros da função estão corretos, e agora você pode escrever um teste que captura o requisito principal dessa função.

Você colabora com colegas para escrever o seguinte teste:

[TestMethod]
public void QuickNonZero()
{
    // Create an instance to test:
    LocalMath math = new LocalMath();

    // Create a test input and expected value:
    var expectedResult = 4.0;
    var inputValue = expectedResult * expectedResult;

    // Run the method:
    var actualResult = math.SquareRoot(inputValue);

    // Validate the result:
    var allowableError = expectedResult/1e6;
    Assert.AreEqual(expectedResult, actualResult, allowableError,
        "{0} is not within {1} of {2}", actualResult, allowableError, expectedResult);
}

Gorjeta

Para essa função, você usa o desenvolvimento test first, no qual você primeiro escreve o teste de unidade para um recurso e, em seguida, escreve o código que satisfaz o teste. Em outros casos, essa prática não é realista, então você escreve os testes depois de escrever o código. Mas é muito importante escrever testes de unidade, seja antes ou depois do código, porque eles mantêm o código estável.

Vermelho, Verde, Refactor...

Siga um ciclo no qual você escreve repetidamente um teste e confirma que ele falha, escreva código para fazer o teste passar e, em seguida, considere a refatoração, ou seja, melhorar o código sem alterar os testes.

Vermelho

Execute todos os testes, incluindo o novo teste que você criou. Depois de escrever qualquer teste, sempre execute-o para certificar-se de que ele falha antes de escrever o código que o faz passar. Por exemplo, se você esquecer de colocar asserções em alguns testes que você escreve, ver o resultado de Reprovação lhe dá confiança de que, quando você o faz passar, o resultado do teste indica corretamente que um requisito foi cumprido.

Outra prática útil é definir Run Tests After Build. Essa opção executa os testes em segundo plano toda vez que você cria a solução, para que você tenha um relatório contínuo do status do teste do seu código. Você pode estar preocupado que essa prática possa tornar o Visual Studio lento para responder, mas isso raramente acontece.

Captura de tela do Test Explorer com um teste reprovado.

Verde

Escreve sua primeira tentativa no código do método que você está desenvolvendo:

public class LocalMath
{
    public double SquareRoot(double x)
    {
        double estimate = x;
        double previousEstimate = -x;
        while (System.Math.Abs(estimate - previousEstimate) > estimate / 1000)
        {
            previousEstimate = estimate;
            estimate = (estimate * estimate - x) / (2 * estimate);
        }
        return estimate;
    }

Execute os testes novamente e todos os testes são aprovados.

Screenshot do Unit Test Explorer com dois testes aprovados.

Refatorização

Agora que o código desempenha sua função principal, olhe para o código para encontrar maneiras de torná-lo melhor ou para torná-lo mais fácil de alterar no futuro. Você pode reduzir o número de cálculos realizados no loop:

public class LocalMath
{
    public double SquareRoot(double x)
    {
        double estimate = x;
        double previousEstimate = -x;
        while (System.Math.Abs(estimate - previousEstimate) > estimate / 1000)
        {
            previousEstimate = estimate; 
            estimate = (estimate + x / estimate) / 2;
            //was: estimate = (estimate * estimate - x) / (2 * estimate);
        }
        return estimate;
    }

Verifique se os testes ainda são aprovados.

Sugestões

  • Cada alteração feita durante o desenvolvimento do código deve ser uma refatoração ou uma extensão:

    • Refatoração significa que você não altera os testes porque não está adicionando novas funcionalidades.
    • Extensão significa adicionar testes e fazer as alterações de código necessárias para passar nos testes existentes e novos.
  • Se você estiver atualizando o código existente para os requisitos que foram alterados, também excluirá testes antigos que não representam mais os requisitos atuais.

  • Evite alterar testes já aprovados. Em vez disso, adicione novos testes. Escreva apenas testes que representem um requisito real.

  • Execute os testes após cada alteração.

... e repetir

Continue sua série de etapas de extensão e refatoração, usando sua lista de pequenos passos como um guia aproximado. Você nem sempre faz uma etapa de refatoração após cada extensão e, às vezes, faz mais de uma etapa de refatoração em sucessão. Mas você sempre executa os testes de unidade após cada alteração no código.

Às vezes, você adiciona um teste que não requer nenhuma alteração no código, mas isso aumenta sua confiança de que o código funciona corretamente. Por exemplo, você deseja ter certeza de que a função funciona em uma ampla gama de entradas. Você escreve mais testes, como este:

[TestMethod]
public void SqRtValueRange()
{
    LocalMath math = new LocalMath();
    for (double expectedResult = 1e-8;
        expectedResult < 1e+8;
        expectedResult = expectedResult * 3.2)
    {
        VerifyOneRootValue(math, expectedResult);
    }
}
private void VerifyOneRootValue(LocalMath math, double expectedResult)
{
    double input = expectedResult * expectedResult;
    double actualResult = math.SquareRoot(input);
    Assert.AreEqual(expectedResult, actualResult, expectedResult / 1e6);
}

Este teste passa na primeira vez que é executado.

Captura de tela do Test Explorer com três testes aprovados.

Apenas para ter certeza de que esse resultado não é um erro, você pode introduzir temporariamente um pequeno erro em seu teste para fazê-lo falhar. Depois de ver a falha, você pode corrigi-lo novamente.

Gorjeta

Sempre faça um teste falhar antes de ser aprovado.

Exceções

Agora passe para a escrita de testes para entradas excecionais:

[TestMethod]
public void RootTestNegativeInput()
{
    LocalMath math = new LocalMath();
    try
    {
        math.SquareRoot(-10.0);
    }
    catch (ArgumentOutOfRangeException)
    {
        return;
    }
    catch
    {
        Assert.Fail("Wrong exception on negative input");
        return;
    }
    Assert.Fail("No exception on negative input");
}

Este teste coloca o código em um loop. Você tem que usar o botão Cancelar no Test Explorer. Isso encerra o código em 10 segundos.

Você quer ter certeza de que um loop infinito não poderia acontecer no servidor de compilação. Embora o servidor imponha um tempo limite em uma execução completa, é um tempo limite muito longo e causaria um atraso substancial. Portanto, você pode adicionar um tempo limite explícito a este teste:

[TestMethod, Timeout(1000)]
public void RootTestNegativeInput()
{...

O tempo limite explícito faz com que o teste falhe.

Atualize o código para lidar com este caso excecional:

public double SquareRoot(double x)
{
    if (x <= 0.0) 
    {
        throw new ArgumentOutOfRangeException();
    }

Regressão

O novo teste passa, mas há uma regressão. Um teste que costumava passar agora falha:

Captura de ecrã do Teste de Unidade que falhou anteriormente e que foi aprovado anteriormente.

Encontre e corrija o erro:

public double SquareRoot(double x)
{
    if (x < 0.0)  // not <=
    {
        throw new ArgumentOutOfRangeException();
    }

Depois de corrigido, todos os testes passam:

Captura de ecrã do Unit Test Explorer com quatro testes aprovados.

Gorjeta

Certifique-se de que todos os testes passam após cada alteração que você faz no código.

Cobertura do código

Em intervalos durante o seu trabalho e, finalmente, antes de fazer check-in do código, obtenha um relatório de cobertura de código. Isso mostra quanto do código foi exercido por seus testes.

Sua equipe tem como objetivo uma cobertura de pelo menos 80%. Eles relaxam esse requisito para o código gerado, porque pode ser difícil alcançar uma alta cobertura para esse tipo de código.

Uma boa cobertura não é uma garantia de que a funcionalidade completa do componente foi testada, e não garante que o código funcionará para cada intervalo de valores de entrada. No entanto, há uma correlação bastante estreita entre a cobertura de linhas de código e a cobertura do espaço comportamental de um componente. Portanto, uma boa cobertura fortalece a confiança da equipe de que está testando a maioria do comportamento que deveria.

Para obter um relatório de cobertura de código, no menu Teste do Visual Studio, selecione Analisar cobertura de código para todos os testes. Todos os testes são executados novamente.

Captura de tela do resultado da cobertura de código e do botão Mostrar cor.

Quando você expande o total no relatório, isso mostra que o código que você está desenvolvendo tem cobertura completa. Isso é muito satisfatório, porque a pontuação importante é para o código em teste. As seções descobertas estão, na verdade, nos próprios testes.

Ao alternar o botão Mostrar Coloração da Cobertura do Código, você pode ver quais partes do código de teste não foram exercitadas. O código que não foi usado nos testes é destacado em laranja. No entanto, essas seções não são importantes para a cobertura porque estão no código de teste e seriam usadas somente se um erro for detetado.

Para verificar se um teste específico atinge ramificações específicas do código, você pode definir Mostrar Coloração da Cobertura do Código e, em seguida, executar o teste único usando o comando Executar no menu de atalho.

Quando terminar?

Você continua a atualizar o código em pequenas etapas até ter certeza de que:

  • Todos os testes unitários disponíveis são aprovados.

    Em um projeto com um conjunto muito grande de testes de unidade, pode ser impraticável para um desenvolvedor esperar que todos eles sejam executados. Em vez disso, o projeto opera um serviço de check-in fechado, no qual todos os testes automatizados são executados para cada prateleira com check-in antes de ser mesclado na árvore de origem. O check-in é rejeitado se a execução falhar. Isso permite que os desenvolvedores executem um conjunto mínimo de testes de unidade em suas próprias máquinas e, em seguida, prossigam com outros trabalhos, sem correr o risco de quebrar a compilação. Para obter mais informações, consulte Usar um processo de compilação de check-in fechado para validar alterações.

  • A cobertura de código atende ao padrão da equipe. 75% é um requisito típico do projeto.

  • Seus testes de unidade simulam todos os aspetos do comportamento necessário, incluindo entradas típicas e excecionais.

  • Seu código é fácil de entender e ampliar.

Quando todos esses critérios forem atendidos, você estará pronto para verificar seu código no controle do código-fonte.

Princípios de desenvolvimento de código com testes unitários

Aplique os seguintes princípios ao desenvolver código:

  • Desenvolva testes de unidade juntamente com o código e execute-os com frequência durante o desenvolvimento. Os testes de unidade representam a especificação do seu componente.
  • Não altere os testes de unidade, a menos que os requisitos tenham mudado ou os testes estejam errados. Adicione novos testes gradualmente à medida que você estende a funcionalidade do código.
  • Procure que pelo menos 75% do seu código seja coberto pelos testes. Observe os resultados da cobertura de código em intervalos e antes de fazer check-in do código-fonte.
  • Faça check-in dos testes de unidade junto com o código, para que eles sejam executados pelas compilações de servidor contínuas ou regulares.
  • Sempre que possível, para cada funcionalidade, escreva primeiro o teste de unidade. Faça isso antes de desenvolver o código que o satisfaz.

Confira as alterações

Antes de fazer check-in das alterações, compartilhe novamente sua tela com os colegas para que eles possam revisar informalmente e interativamente com você o que você criou. Os testes continuam a ser o foco da sua discussão com colegas que estão principalmente interessados no que o código faz, não em como ele funciona. Esses colegas devem concordar que o que você escreveu atende às necessidades deles.

Faça check-in de todas as alterações feitas, incluindo os testes e o código, e associe-as às tarefas concluídas. O check-in enfileira o sistema automatizado de compilação de equipe da equipe para validar suas alterações usando o processo de compilação do CI Build da equipe. Esse processo de compilação ajuda a equipe a minimizar erros em sua base de código, criando e testando, em um ambiente limpo separado de seus computadores de desenvolvimento, cada alteração que a equipe faz.

Você será notificado quando a compilação for concluída. Na janela de resultados da compilação, você vê que a compilação foi bem-sucedida e todos os testes foram aprovados.

Para verificar as alterações

  1. Na página Meu Trabalho no Team Explorer, selecione Check-in.

    Captura de ecrã do check-in a partir de O Meu Trabalho.

  2. Na página Alterações Pendentes, certifique-se de que:

    • Todas as alterações relevantes estão listadas em Alterações incluídas.
    • Todos os itens de trabalho relevantes estão listados em Itens de Trabalho Relacionados.
  3. Insira um comentário para ajudar sua equipe a entender o objetivo dessas alterações quando examinar o histórico de controle de versão dos arquivos e pastas alterados.

  4. Escolha Check-in.

    Captura de ecrã a mostrar o check-in das Alterações Pendentes.

Para integrar continuamente o código

Para obter mais informações sobre como definir um processo de compilação de integração contínua, consulte Configurar uma compilação de CI. Depois de configurar esse processo de compilação, você pode optar por ser notificado sobre os resultados das compilações de equipe.

Captura de tela da página Minhas compilações com uma compilação bem-sucedida.

Para obter mais informações, consulte Executar, monitorar e gerenciar compilações.

Próximos passos