Um dia na vida de um desenvolvedor de devops: gravar novo código para uma história de usuário

Azure DevOps Services | 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 compilar seu aplicativo. O tutorial fornece exemplos de como você pode usar o Visual Studio e o TFVC para fazer check-out e atualizar códigos, 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, ela configura seus computadores cliente e servidor, cria uma lista de pendências, planeja uma iteração e conclui outros planejamentos necessários para começar a desenvolver seu aplicativo.

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

As ferramentas Meu Trabalho e Revisão de Códigodo Visual Studio ajudam os desenvolvedores a gerenciar seu trabalho e colaborar com colegas.

Observação

Os recursos do Visual Studio Meu Trabalho e Revisão de Código estão disponíveis nas seguintes edições:

  • Visual Studio 2022: Visual Studio Community, 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 iniciar o trabalho

A equipe concordou que, durante o sprint atual, você trabalhará no status Avaliar 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 filha do item da lista de pendências de prioridade máxima.

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

Para revisar a lista de pendências e preparar tarefas para iniciar o trabalho

Captura de tela da página Meu Trabalho.

  1. No Team Explorer, se você ainda não estiver conectado ao projeto da equipe 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.

    Você também pode selecionar uma tarefa na lista Itens de Trabalho Disponíveis e selecionar Iniciar.

Plano de trabalho incremental de rascunho

Você desenvolve código em uma série de pequenas etapas. Cada etapa demora normalmente não mais que uma hora e algumas podem demorar dez minutos. Em cada etapa,você grava um novo teste de unidade e altera o código que está desenvolvendo para que ele passe no novo teste, além dos testes já gravados. Às vezes, você grava o novo teste antes de alterar o código e, às vezes, altera o código antes de gravar o teste. Às vezes, você refatora. Ou seja, você apenas melhora o código sem adicionar novos testes. Você nunca altera um teste 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 essa área do código. Você não considera a etapa concluída até que cada teste seja aprovado.

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

Você pode escrever um plano de rascunho para essa sequência de pequenas etapas. Você sabe que os detalhes e a ordem exata das etapas posteriores mudarão provavelmente como você trabalha. Esta é a lista inicial de etapas para essa tarefa específica:

  1. Criar o stub do método de teste, ou seja, apenas a assinatura do método.
  2. Atender a um caso típico específico.
  3. Testar o intervalo amplo. Garantir que o código responda corretamente a um intervalo grande de valores.
  4. Exceção no negativo. Lidar normalmente com parâmetros incorretos.
  5. Cobertura de código. Garantir que pelo menos 80% do código seja utilizado pelos testes de unidade.

Alguns desenvolvedores escreve esse tipo de plano em comentários em seu código de teste. Outros apenas memorizam seu plano. Pode ser útil escrever sua lista de etapas no campo Descrição do item de trabalho Tarefa. Caso você tenha de alternar temporariamente para uma tarefa mais urgente, você saberá onde encontrar a lista quando puder retornar a ela.

Criar o primeiro teste de unidade

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

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

  1. Clique em Arquivo>Novo Projeto.
  2. Na caixa de diálogo Criar um novo projeto , selecione a seta ao lado de Todas as linguagens 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 Avançar e, em seguida, selecione Criar.

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

No editor de código, substitua o conteúdo de UnitTest1.cs pelo seguinte código. Nessa fase, você quer apenas ilustrar como um de 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ê grava o exemplo em um método de teste porque, quando grava o código, deseja que o exemplo funcione.

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

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

Este tutorial usa o Framework de Teste de Unidade do Visual Studio, mas você também pode usar frameworks de outros provedores. O Gerenciador de Testes funciona igualmente bem com outros frameworks, 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 que é fornecida. Cada teste de unidade é um método.

    • Cada teste de unidade deve ser prefixado pelo atributo TestMethod, e o método de teste de unidade não deve ter parâmetros. Você pode usar o nome que quiser 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 classe Assert, para indicar se foi aprovado ou reprovado. Normalmente, você verifica se os resultados esperados e reais de uma operação são iguais:

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

    • Você pode organizar seus testes em mais de uma classe. Cada classe deve ser prefixada pelo atributo TestClass.

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

Para obter informações sobre como gravar testes de unidade no C++, consulte Gravação de 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 existe 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 de Teste e Classe.

No novo projeto, você adiciona a nova classe e uma versão mínima do método que permitirá pelo menos 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

Primeiramente, crie o projeto em que você deseja adicionar a nova classe, a menos que já exista.

Para gerar uma classe

  1. Coloque o cursor em um exemplo da classe que você deseja gerar, por exemplo, LocalMath, e selecione Ações Rápidas e Refatorações.
  2. No menu de atalho, escolha Gerar novo tipo.
  3. Na caixa de diálogo Gerar Tipo, defina Projeto para o projeto de 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ções.
  2. No menu de atalho, escolha Gerar método 'SquareRoot'.

Executar o primeiro teste

Compilar e executar o teste. O resultado do teste mostra um indicador vermelho de Reprovado e o teste aparece na lista de Testes Reprovados.

Captura de tela do Gerenciador de Testes 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 o teste é aprovado.

Captura de tela do Gerenciador de Testes de Unidade 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 Teste estiver aberto, escolha Executar ou Executar Todos os Testes no Modo de Exibição.

Captura de tela do Gerenciador de Testes mostrando o botão Executar Todos.

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 do resultado do teste, selecione o teste no Gerenciador de Testes.

  • Para navegar para 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 você compilar a solução, selecione a seta ao lado do ícone Configurações e, em seguida, selecione Executar Testes Após 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 esse teste foi apenas para garantir que o nome e os parâmetros da função estejam corretos e agora você pode gravar um teste que captura o requisito principal desta função.

Você colabora com colegas para gravar 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);
}

Dica

Para essa função, use desenvolvimento de primeiro teste, em que você primeiro grava o teste de unidade de um recurso e depois grava o código que atende ao teste. Em outros casos, essa prática não é realista, portanto, você grava os testes depois de gravar o código. Mas é muito importante gravar testes de unidade, seja antes ou depois do código, porque mantém o código estável.

Vermelho, Verde, Refatorar…

Siga um ciclo em que você grava repetidamente um teste e confirma se ele falha, grave o código para fazer o teste ser aprovado e então 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 gravar qualquer teste, sempre o execute para verificar se o teste falha antes de gravar o código que o faz ser aprovado. Por exemplo, se você esquecer de colocar declarações em alguns testes que grava, ver o resultado Reprovado lhe dará confiança de que, quando você o fizer ser aprovado, o resultado do teste indicará corretamente que um requisito foi atendido.

Outra prática útil é definir a Executar Testes após Compilação. Essa opção executará os testes em segundo plano cada vez que você compilar a solução, para que você tenha um relatório contínuo do status de teste de 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 Gerenciador de Testes com um teste reprovado.

Verde

Você grava sua primeira tentativa no código do método que 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.

Captura de tela do Gerenciador de Testes de Unidade com dois testes aprovados.

Refatorar

Agora que o código executa sua função principal, analise o código para encontrar formas de fazê-lo funcionar melhor ou para facilitar a alteração no futuro. Você pode reduzir o número de cálculos executados 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.

Dicas

  • 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 uma nova funcionalidade.
    • Extensão significa adicionar testes e fazer alterações no código que são necessárias para aprovar testes novos e existentes.
  • Se você for atualizar o código existente de acordo com requisitos que foram alterados, também excluirá os testes antigos que não representam mais os requisitos atuais.

  • Evite alterar os testes que já passaram. Em vez disso, adicione novos testes. Escreva somente os testes que representam um requisito real.

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

… e repita

Continue sua série de etapas de extensão e refatoração, usando a lista de pequenas etapas como um guia de referência. Você nem sempre executa uma etapa de refatoração depois de cada extensão e, às vezes, executa mais de uma etapa de refatoração sucessivamente. 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 alteração no código, mas que aumenta sua confiança de que o código funciona corretamente. Por exemplo, você quer ter certeza de que a função trabalha em um amplo intervalo de entradas. Você grava 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);
}

O teste é aprovado na primeira vez em que é executado.

Captura de tela do Gerenciador de Testes com três testes aprovados.

Apenas para confirmar se esse resultado não é um engano, você introduz temporariamente um pequeno erro nesse teste para fazê-lo falhar. Depois de ver a falha, você pode corrigi-la novamente.

Dica

Sempre faça um teste falhar antes de fazê-lo passar.

Exceções

Agora, você passa a gravar testes para entradas excepcionais:

[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");
}

Esse teste coloca o código em um loop. Você tem de usar o botão Cancelar do Gerenciador de Testes. Isso encerra o código dentro de 10 segundos.

Você quer garantir que um loop infinito não possa ocorrer no servidor de compilação. Embora o servidor imponha um tempo limite para a conclusão da execução, é um tempo limite muito longo e causaria um atraso significativo. Portanto, você pode adicionar um tempo limite explícito a este teste:

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

O tempo limite explícito faz o teste falhar.

Atualize o código para tratar deste caso excepcional:

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

Regressão

O novo teste é aprovado, mas há uma regressão. Um teste que antes passava, agora falha:

Captura de tela do Teste de Unidade reprovado que foi aprovado anteriormente.

Localize e corrija o erro:

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

Depois de corrigidos, todos os testes são aprovados:

Captura de tela do Gerenciador de Testes de Unidade com quatro testes aprovados.

Dica

Certifique-se de que cada teste passe após cada alteração feita no código.

Cobertura de código

Em intervalos durante seu trabalho e, por fim, antes de fazer check-in no código, obtenha um relatório de cobertura de código. Ele mostra quanto do código foi utilizado por seus testes.

A sua equipe quer uma cobertura de pelo menos 80%. Eles relevam esse requisito para o código gerado, pois pode ser difícil obter uma alta cobertura para esse tipo de código.

A 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. Entretanto, há uma correlação razoavelmente próxima entre a cobertura das linhas de código e a cobertura do espaço comportamental de um componente. Portanto, a boa cobertura reforça a confiança da equipe de que está testando o máximo possível do comportamento.

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, ele mostra que o código que você está desenvolvendo tem cobertura completa. Isso é muito satisfatório, pois a contagem é importante para o código em teste. As seções descobertas estão realmente nos testes em si.

Ativando/desativando o botão Mostrar Coloração de Cobertura de Código, você pode ver quais partes do código de teste não foram utilizadas. O código que não foi usado nos testes é realçado em laranja. No entanto, essas seções não são importantes para a cobertura porque elas estão no código de teste e seriam usadas apenas se um erro fosse detectado.

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

Quando você termina?

Você continua a atualizar o código em pequenas etapas até ficar satisfeito que:

  • Todos os testes de unidade disponíveis passam.

    Em um projeto com um conjunto muito grande de testes de unidade, pode ser impraticável para um desenvolvedor aguardar a execução de todos eles. Em vez disso, o projeto opera um serviço de check-in restrito, em que todos os testes automatizados são executados para cada check-in particular antes de ser mesclado na árvore de fonte de dados. O check-in será rejeitado se a execução falhar. Isso permite que os desenvolvedores executem um conjunto mínimo de testes de unidade em seu próprio computador e depois continue com outro trabalho, sem correr o risco de interromper a compilação. Para obter mais informações, consulte Usar um processo de compilação de check-in restrito para validar as alterações.

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

  • Os testes de unidade simulam cada aspecto do comportamento exigido, inclusive entradas típicas e excepcionais.

  • O seu código é fácil de reconhecer e estender.

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

Princípios do desenvolvimento de código com testes de unidade

Aplique os seguintes princípios durante o desenvolvimento do código:

  • Desenvolva testes de unidade juntamente com o código e execute-os frequentemente durante o desenvolvimento. Os testes de unidade representam a especificação de seu componente.
  • Não altere os testes de unidade, a menos que os requisitos mudem ou os testes estejam incorretos. Adicione novos testes gradativamente conforme você estende a funcionalidade do código.
  • Defina como meta pelo menos 75% de cobertura de seu código 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 juntamente com o código, para que sejam executados por compilações contínuas ou regulares de servidor.
  • Quando possível, para cada parte da funcionalidade, escreva o teste de unidade primeiro. Faça isso antes de desenvolver o código que satisfaça isso.

Fazer check-in das alterações

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

Verifique todas as alterações feitas, incluindo os testes e o código, e associe-as às tarefas que você concluiu. O check-in enfileira o sistema de compilação automatizada da equipe para validar as suas alterações usando o processo de compilação Compilação de CI da equipe. Esse processo de compilação ajuda a equipe a minimizar erros em sua base de código compilando e testando, em um ambiente limpo e separado dos computadores de desenvolvimento, cada alteração que a equipe faz.

Você é notificado quando a compilação é concluída. Na janela de resultados da compilação, você vê que a compilação teve êxito e todos os testes foram aprovados.

Para fazer check-in das alterações

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

    Captura de tela do check-in de Meu Trabalho.

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

    • Todas as alterações relevantes são listadas em Alterações Incluídas.
    • Todos os itens de trabalho relevantes são listados em Itens de Trabalho Relacionados.
  3. Especifique um Comentário para ajudar sua equipe a entender a finalidade dessas alterações quando verificarem o histórico do controle de versão dos arquivos e pastas alterados.

  4. Escolha Fazer Check-in.

    Captura de tela de check-in das Alterações Pendentes.

Para integrar o código continuamente

Para obter mais informações sobre como definir um processo de compilação de integração contínua, consulte Definir 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 da 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óximas etapas