Share via


Escrever um novo código para uma história de usuário

Você é um novo usuário do Visual Studio Application Lifecycle Management (ALM) e do Team Foundation Server (TFS)? Quer saber como você e sua equipe podem obter o máximo benefício de versão mais recentes dessas ferramentas para criar seu aplicativo?

Em alguns minutos, você terá o passo a passo neste tutorial de dois capítulos e acompanhará um dia na vida de Peter e Julia, dois desenvolvedores da Fabrikam Fiber, um empresa fictícia que fornece serviços de televisão a cabo e serviços relacionados. Você verá exemplos de como pode usar o Visual Studio e o TFS para verificar e atualizar códigos, suspender o trabalho quando você for interrompido, solicitar uma análise do código, fazer check-in de suas alterações e executar outras tarefas.

A história até o momento

Recentemente, a equipe começou a adotar o Visual Studio e o Team Foundation Server para Gerenciamento do Ciclo de Vida do Aplicativo (ALM). Ela configurou seus computadores servidor e cliente, criou uma lista de pendências, planejou uma iteração e concluiu outro planejamento necessário para começar a desenvolver seu aplicativo.

Visão geral deste capítulo

Peter revisa brevemente sua lista de pendências e seleciona a tarefa em que ele trabalhará hoje. Ele escreve testes de unidade para o código que planeja desenvolver. Normalmente, ele executa os testes várias vezes em uma hora, escrevendo gradativamente testes mais detalhados e escrevendo o código que faz com que eles passem. Ele conversa frequentemente sobre a interface de seu código com seus colegas que usarão o método que está escrevendo.

Dica

Os recursos Meu Trabalho e Cobertura de Código abordados neste tópico só estão disponíveis no Visual Studio Premium e no Visual Studio Ultimate.

Neste tópico

  • Revisar a lista de pendências pessoal e preparar tarefas para iniciar o trabalho

  • Criar o primeiro teste de unidade

  • Criar um stub para o novo código

  • Executar o primeiro teste

  • Concordar com a API

  • Vermelho, Verde, Refatorar…

  • Cobertura de código

  • Quando terminamos?

  • Fazer check-in das alterações

Revisar a lista de pendências pessoal e preparar tarefas para iniciar o trabalho

No Team Explorer, Peter abre a página Meu Trabalho. A equipe concordou que, durante o sprint atual, Peter trabalhará no status Avaliar fatura, um item de prioridade máxima na lista de pendências do produto. Peter decide começar com Implementar funções matemáticas, uma tarefa filho do item da lista de pendências de prioridade máxima. Ele arrasta essa tarefa da lista Itens de Trabalho Disponíveis para a lista Itens de Trabalho e Alterações em Andamento.

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

A lista na página Meu trabalho no navegador de equipe

  1. No Team Explorer:

    1. Se você ainda não estiver conectado ao projeto da equipe no qual deseja trabalhar, conecte-se ao projeto da equipe.

    2. Escolha Ícone da home page Página Inicial e depois Ícone Meu trabalho Meu Trabalho.

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

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

Plano de trabalho incremental de rascunho

Normalmente, Peter 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, ele 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 já escritos. Às vezes, ele escreve o novo teste antes de alterar o código e, às vezes, altera o código antes de escrever o teste. Às vezes, ele refatora. Ou seja, ele apenas melhora o código sem adicionar novos testes. Ele nunca modifica um teste aprovado, a menos que decida que ele não representou corretamente um requisito.

No final de cada pequena etapa, ele executa todos os testes de unidade que são relevantes para essa área do código. Ele não considera a etapa concluída até que cada teste seja aprovado.

No entanto, ele não verificará o código no Team Foundation Server até que tenha terminado a tarefa inteira.

Peter escreve um plano de rascunho para essa sequência de pequenas etapas. Ele sabe que os detalhes e a ordem exata das etapas posteriores mudarão provavelmente como ele trabalha. Esta é a lista inicial de etapas criada por ele 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 de seus colegas escreve esse tipo de plano em comentários em seu código de teste. Outros apenas memorizam seu plano. Peter acha útil escrever sua lista de etapas no campo Descrição do item de trabalho Tarefa. Caso ele tenha de alternar temporariamente para uma tarefa mais urgente, ele saberá onde encontrar a lista quando puder retornar.

Criar o primeiro teste de unidade

Peter começa criando um teste de unidade. Ele começa 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 ele está testando. Então, ele cria um novo projeto de teste de unidade. Ele abre a caixa de diálogo Novo Projeto, escolhe Visual C#, Testar e Projeto de Teste Unitário.

Teste de unidade selecionada na caixa de diálogo Novo projeto

O projeto de teste de unidade fornece um arquivo C# em que ele pode escrever seu exemplo. Nessa fase, ele 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);
        }
    }
}

Ele escreve o exemplo em um método de teste porque, quando escrever seu código, ele vai querer que o exemplo funcione.

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

Geralmente, você criaria 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 procedimento usa o Visual Studio Unit Test Framework, 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.

  • Crie um projeto de teste, se ainda não existir.

    • Na caixa de diálogo Novo Projeto, escolha uma linguagem, como Visual Basic, Visual C++ ou Visual C#. Escolha Testar e depois Projeto de Teste Unitário.
  • 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 mais 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, Peter cria 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. Ele adiciona uma referência de projeto do projeto de teste ao código em desenvolvimento.

Gerenciador de soluções com projetos de teste e classe

No novo projeto, ele 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. No menu de atalho, escolha Gerar Código, Novo Tipo.

  2. Na caixa de diálogo Novo Tipo, defina Projeto para o projeto de biblioteca de classes. Neste exemplo, é Fabrikam.Math.

Para gerar um método

  • Coloque o cursor em uma chamada para o método, por exemplo, SquareRoot. No menu de atalho, escolha Gerar Código, Stub do Método.

Executar o primeiro teste

Peter compila e executa o teste pressionando CTRL+R, T. O resultado do teste mostra um indicador vermelho de reprovado e o teste aparece na lista Testes com Falha.

Gerenciador de testes de unidade mostrando um teste com falha

Ele faz uma alteração simples no código:

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

Ele executa o teste novamente, e o teste é aprovado:

Gerenciador de testes de unidade com um passou no teste

Para executar testes de unidade

Mostrar botão Executar todos do Gerenciador de testes

  • No menu Testar, escolha Executar, Todos os Testes.

    - ou -

  • Se o Gerenciador de Testes estiver aberto, escolha Executar Tudo.

    - ou -

  • Coloque o cursor em um arquivo de código de teste e pressione CTRL+R, T.

  • Se um teste aparecer em Testes com Falha:

    Abra o teste, por exemplo, clicando duas vezes no nome.

    O ponto em que o teste falhou é exibido.

Para ver uma lista completa de testes, escolha Mostrar Tudo. Para retornar ao resumo, escolha a exibição INÍCIO.

Para ver os detalhes do resultado de um 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 Testes Selecionados.

Para executar testes em segundo plano sempre que você compilar a solução, ativar/desativar Executar Testes após Compilação. Os testes que falharam anteriormente são executados primeiro.

Concordar com a interface

Peter chama sua colega Julia no Lync e compartilha a tela dele. Ela usará o componente dele. Ele mostra seu exemplo inicial.

Julia acha que o exemplo está OK, mas comenta: "Muitas funções passariam nesse teste.”

Peter responde: “O primeiro teste é apenas para garantir que o nome e os parâmetros da função estejam corretos. Agora podemos escrever um teste que capture o requisito principal dessa função.”

Juntos, eles escrevem 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, Peter está usando o recurso Test First Development, em que ele primeiro escreve o teste de unidade de um recurso e depois escreve o código que atende ao teste.Em outros casos, ele acha que essa prática não é realista. Então, ele escreve os testes depois de escrever o código.Mas ele a considera muito importante para escrever testes de unidade – seja antes ou depois do código – porque mantém o código estável.

Vermelho, Verde, Refatorar…

Peter segue um ciclo em que escreve repetidamente um teste e confirma se ele falha, escreve o código para fazer o teste passar e considera a refatoração, ou seja, melhorar o código sem alterar os testes.

Vermelho

Peter pressiona CTRL+R, T para executar o novo teste que ele criou com Julia. Depois de escrever qualquer teste, ele sempre o executa para verificar se o teste falha antes de escrever o código que o faz ser aprovado. Essa é uma prática que ele aprendeu depois que se esqueceu de colocar asserções em alguns testes que escreveu. Ver o resultado de falha dá a ele a confiança de que, quando ele fizer o teste ser aprovado, o resultado do teste indicará corretamente que um requisito foi atendido.

Outra prática útil é definir a opção 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. A princípio, Peter suspeitava que isso poderia deixar o Visual Studio lento para responder, mas ele acha que isso raramente acontece.

Gerenciador de testes de unidade com um teste com falha

Verde

Peter escreve 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;
        }
        

Peter executa os testes novamente e todos os testes passam:

Gerenciador de testes de unidade com dois testes aprovados

Refatorar

Agora que o código executa sua função principal, Peter analisa o código para encontrar formas de fazê-lo funcionar melhor ou para facilitar a alteração no futuro. Ele percebe que 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;
        }

Ele verifica se os testes ainda passam:

Gerenciador de testes de unidade com dois testes aprovados

Dica

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

Peter continua sua série de etapas de extensão e refatoração, usando a lista de pequenas etapas como um guia de referência. Ele nem sempre executa uma etapa de refatoração depois de cada extensão, e às vezes ele executa mais de uma etapa de refatoração sucessivamente. Mas ele sempre executa os testes de unidade após cada alteração no código.

Às vezes, ele 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, ele quer ter certeza de que a função funcione em um amplo intervalo de entradas. Ele 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);
        }

O teste passa na primeira vez em que é executado:

Gerenciador de testes de unidade com três testes aprovados

Apenas para confirmar se esse resultado não é um engano, ele introduz temporariamente um pequeno erro nesse teste para fazê-lo falhar. Depois de ver a falha, ele corrige o código novamente.

Dica

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

Exceções

Agora, Peter passa a escrever 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. Ele tem de usar o botão Cancelar do Gerenciador de Testes. Isso encerra o código dentro de 10 segundos.

Peter 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, ele adiciona um tempo limite explícito a este teste:

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

O tempo limite explícito faz o teste falhar.

Peter atualiza 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:

Falha no teste de unidade que já passou

Peter encontra e corrige o erro:

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

Depois de corrigidos, todos os testes passam:

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

Nos intervalos durante seu trabalho e, finalmente, antes de fazer check-in do código, Peter obtém um relatório de Cobertura de Código. Ele mostra quanto do código foi utilizado por seus testes.

A equipe de Peter 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 Testes, escolha Executar, Analisar Cobertura de Código de Todos os Testes. Em seguida, execute todos os testes novamente.

Resultados de cobertura e botão Mostrar cores no código

Peter obtém uma cobertura total de 86%. Quando ele expande o total no relatório, o documento mostra que o código que está desenvolvendo tem uma cobertura de 100%. 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, Peter pode ver quais partes do código de teste não foram utilizadas. No entanto, ele decide que 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 usar a opção Mostrar Coloração de Cobertura de Código e executar o único teste usando o comando Executar no menu de atalho.

Quando terminamos?

Peter 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 restringido, em que todos os testes automatizados são executados para cada check-in particular antes de ser mesclado na árvore de código-fonte. O check-in será rejeitado se a execução falhar. Isso permite que o desenvolvedor execute 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 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 código dele é fácil de entender e estender.

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

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

Peter aplica 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 normais 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 de suas alterações, Peter usa novamente o Lync para compartilhar a tela dele com sua colega Julia para que ela possa revisar com ele o que ele criou. Os testes continuam a ser o foco de conversa porque Julia está basicamente interessada no que o código faz, e não em como ele funciona. Julia concorda que Peter que escreveu o que ela precisa.

Peter faz check-in em todas as alterações feitas, incluindo os testes e o código, e os associa à tarefa que foi concluída. O check-in enfileira o sistema de compilação automatizada da equipe para validar as alterações dele usando o processo de compilação 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.

Peter é notificado quando a compilação é concluída. Na janela de resultados da compilação, ele vê que a compilação teve êxito e todos os testes passaram.

Para fazer check-in das alterações

Fazer check-in de alterações pendentes

  1. Na barra de menus, escolha Exibir, Team Explorer.

  2. No Team Explorer, escolha Página Inicial e Meu Trabalho.

  3. Na página Meu Trabalho, escolha Fazer Check-in.

  4. Analise o conteúdo da página Alterações Pendentes para verificar se:

    • 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.

  5. 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.

  6. Escolha Fazer Check-in.

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 Configurar uma compilação CI. Depois de configurar esse processo de compilação, você pode optar por ser notificado sobre os resultados das compilações da equipe.

Peter é notificado de que a compilação CI bem-sucedido

Resultados de compilação CI

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

A seguir (Suspender trabalho, corrigir um bug e conduzir uma análise de código)