Diretriz: Desenvolvimento Dirigido por Testes (TDD)
Relacionamentos
Elementos Relacionados
Descrição Principal

Tópicos

O que é TDD?

TDD é a prática de escrever testes de unidade e código de produção concorrentemente e com alto nível de detalhe. Um par de programadores escreve primeiro uma pequena parte de um teste de unidade e, em seguida, escrevem código de produção suficiente apenas para fazer este teste de unidade compilar e executar. Então eles escrevem um pouco mais do teste e acrescentam código de produção suficiente para fazer com que esse novo teste compile e execute com sucesso. Este ciclo pode durar de 30 segundos a cinco minutos. Raramente alcança dez minutos. Em cada ciclo, os testes vêm em primeiro lugar. Uma vez que um teste de unidade esteja pronto, eles constroem outro até que todos os testes da tarefa em que estão trabalhando estejam prontos.

Um exemplo de TDD em Java

Segue um exemplo simples de desenvolvimento dirigido por testes. O programa que estamos escrevendo é um formatador de texto que pode obter strings arbitrárias e centralizá-las horizontalmente em uma página. A primeira coluna mostra os testes, e a segunda coluna mostra o código de produção. O teste é sempre escrito e compilado primeiro. Se a compilação falhar, então será acrescentado código de produção para compilar com êxito. Então o teste é executado para ver se passa. Se o teste falhar, então código de produção é acrescentado para fazer o teste passar. Se o teste passar, então um novo teste é acrescentado.

Primeiro escrevemos o teste
Então escrevemos o código de produção

Quais são os benefícios do TDD?

  • Cobertura de Teste. Se você seguir as regras do TDD, então praticamente 100% das linhas de código de produção em seu programa estarão cobertos por testes de unidade. Isto não cobre 100% dos caminhos no código, mas certifica que praticamente todas as linhas são executadas e testadas.
  • Repetitividade dos Testes. Os testes podem ser executados a qualquer hora que você quiser. Isto é especialmente útil após você ter feito alterações no código de produção. Você pode executar os testes para certificar-se que o código existente não parou de funcionar. Visto que os testes lhe dão segurança você pode ter coragem de fazer alterações que de outra forma seriam muito arriscadas de fazer.
  • Documentação. Os testes descrever o seu entendimento de como o código deve se comportar. Eles também descrevem a API. Por isso, os testes são uma forma de documentação. Os testes de unidade são normalmente muito simples e, portanto, são fáceis de entender. Além disso, são inequívocos e executáveis. Finalmente, se os testes forem executados todas às vezes que qualquer alteração no código for feita, eles nunca ficarão obsoletos.
  • Design da API. Quando você escreve os testes primeiro, você se coloca na posição de um usuário da API de seu programa. Isso só pode lhe ajudar a executar um melhor design da API. Sua primeira preocupação, ao escrever os testes, é tornar mais fácil e conveniente o uso dessa API.
  • Design do Sistema. Um módulo que é independentemente testável é um módulo que está desacoplado do resto do sistema. Quando você escrever os testes primeiro, você automaticamente desacopla os módulos que você está testando. Isso tem um profundo efeito positivo na qualidade geral do design do sistema.
  • Depuração Reduzida. Quando você se move de acordo com os minúsculos passos recomendados pelo TDD, quase nunca é necessário usar o depurador. O tempo de depuração é reduzido extremamente.
  • O seu código acabou de funcionar! se você observar uma equipe de desenvolvedores que estão praticando TDD, você perceberá que cada par de desenvolvedores teve seu código funcionando a um minuto atrás. Não importando quando você faz a observação! A um minuto ou mais atrás, cada par executou seu código, e ele passou em todos os testes. Sendo assim, você nunca estará muito longe de fazer o sistema funcionar.

Quais são os custos do TDD?

  • A programação em pequenos ciclos pode parecer ineficaz. Os programadores muitas vezes acham frustrante trabalhar em incrementos tão pequenos que lhes permitam saber o resultado do teste. Às vezes parece que não vale a pena dar um passo tão pequeno.
  • Muito código de teste é produzido. Não é raro que o total de código de teste exceda em muito o total de código de produção. Este código tem que ser mantido a um custo significativo.
  • Bastante tempo é gasto mantendo os testes sincronizados com o código de produção. Os programadores, por vezes acham que o tempo gasto para manter os testes funcionando e bem estruturados é um tempo que não está sendo gasto com os desejos do cliente.

Quais princípios de teste eu devo empregar?

  • Isolamento. Ao escrever um teste de unidade para um módulo, considere se você deseja que o módulo invoque outros módulos. Se não desejar, então isole o módulo com interfaces. Por exemplo, suponha que você está testando um módulo que interage com o banco de dados. O teste não tem nada a ver com o banco de dados; ele simplesmente testa a forma que o módulo manipula o banco de dados. Então você isola o módulo, do banco de dados, criando uma interface que represente o banco de dados para que o módulo use. Então, para os propósitos do teste, você implementa a interface com um stub de teste. Este tipo de isolamento diminui consideravelmente a quantidade de acoplamento, em todo o sistema.

  • Simplicidade Mantenha os ciclos de edição, compilação e testes extremamente curtos: menos de cinco minutos em média. Escreva somente o código de produção necessário para fazer os testes atuais passarem. Tente não escrever código que irá fazer futuros testes passarem. Em todos os ciclos de edição, compilação e testes, mantenha o código o mais simples possível.
  • Aumento da Generalidade. À medida que você adiciona casos de teste, o código de produção deve tornar-se mais e mais geral. Tente sempre aumentar a generalidade. Por exemplo, considere o seguinte caso de teste:

    Poderíamos fazer esse teste passar escrevendo:

Isto está de acordo com o princípio de simplicidade. Se testThreeSquared fosse o único caso de teste que importasse então esta implementação estaria correta. É claro que sabemos que está incorreta, mas na sua forma atual ele verifica que o caso de teste realmente passa quando se espera. Agora suponha que acrescentemos um novo caso de teste:

Poderíamos fazê-lo passar, alterando a função square da seguinte forma:

Mesmo passando no teste, ela viola a regra de tornar o código mais geral. Para tornar o código mais geral, temos que retornar a raiz do argumento.

Esta solução passa em todos os testes, é simples, e aumenta a generalidade da solução.

  • Casos Incomuns e Condições Limítrofes. Os casos incomuns e as condições limítrofes são implementados no código de produção com declarações if ou outras estruturas similares de decisão. Não escreva estas declarações a menos que você tenha um teste de unidade que esteja falhando porque elas não existem. Por exemplo, digamos que você está calculando um pagamento semanal para um empregado horista.

    O código que permite ele passar tem esta aparência:

Agora vamos dizer que queremos calcular o pagamento de horas extras. Qualquer hora após as oito é cobrada uma hora e meia. A primeira coisa que fazemos é adicionar o novo caso de teste falho:

Então fazemos o caso de teste passar, alterando o código de produção.

Evite acrescentar qualquer if, while, for, do ou qualquer outro tipo de condicional sem um caso de teste falhar. Lembre-se de acrescentar casos de teste para cada condição limítrofe.

  • Teste Tudo Que Possa Falhar. Do mesmo modo, não se preocupe em testar coisas que não podem falhar. Por exemplo, normalmente é infrutífero testar accessors e mutators simples.

    Acessors e mutators normalmente não podem falhar. Por isso não há razão para testá-los. Um julgamento claro tem que ser aplicado para usar esta regra. Você será tentado a evitar um teste de unidade necessário, alegando que o código não pode falhar. Você saberá que caiu neste hábito quando começar a encontrar erros nos métodos que você pensava que não podiam falhar.

  • Mantenha os Dados Dos Testes no Código. Às vezes é tentador colocar dados de teste em um arquivo, especialmente quando a entrada de um módulo é um arquivo. Entretanto, o melhor lugar para os dados do teste é no próprio código do teste de unidade. Por exemplo, suponha que temos uma função que conte a quantidade de caracteres em um arquivo. A assinatura desta função é:

    Para manter os dados de teste no código do teste de unidade, o teste deve ser escrito desta forma:

Isto mantém todos os dados relevantes para o teste em um só lugar.

  • Remoção de Testes. Algumas vezes você vai escrever testes que são úteis por um tempo, mas tornam-se redundantes à medida que outros testes assumam o seu papel. Não tenha medo de remover antigos testes redundantes. Mantenha a suíte de testes o menor possível sem comprometer a cobertura.
  • Mantenha a Duração do Teste Curta. A eficácia dos testes depende da conveniência. A eficácia dos testes depende da conveniência. Sendo assim, é muito importante manter a duração dos testes bem curta. Em um sistema grande, isto significa decompor os testes.

    Ao trabalhar em um módulo em particular, você vai querer escolher os testes que sejam relevantes para esse módulo e os módulos circundantes. Mantenha a duração do teste bem abaixo de um minuto. Dez segundos é frequentemente muito tempo.

    Ao fazer check-in, de um módulo, execute uma suíte que teste todo o sistema, mas que não dure mais do que 10 minutos para ser executada. Isto pode significar que você terá que extrair alguns dos testes mais demorados.

    Execute a totalidade dos testes no sistema, todas as noites. Mantenha o tempo de execução suficientemente pequeno para que eles possam ser executados mais de uma vez antes do amanhecer, para os casos onde haja problemas que obriguem outra execução.

Como posso testar GUIs?

O truque para escrever testes de unidade para GUIs é a separação e o desacoplamento. Separe o código da GUI em três camadas, normalmente chamadas Modelo, Visão e Apresentador:

  • O Modelo interpreta as regras de negócio dos itens que serão exibidos na tela. Todas as políticas relevantes relacionadas ao negócio são implementadas neste módulo. Por isso, este módulo é fácil de testar somente com base em suas entradas e saídas.
  • O Apresentador entende como os dados serão apresentados e como o usuário irá interagir com estes dados. Ele sabe que existem botões, caixas de verificação, campos de texto, etc. Ele sabe que às vezes os botões precisam ser desativados (esmaecidos) e os campos de texto não editáveis. Ele sabe, em um nível mecânico, como os dados são exibidos e como as interações ocorrem. Entretanto, ele não sabe nada sobre a real API da GUI. Por exemplo, se você estiver escrevendo uma GUI Java Swing, o Apresentador não usará nenhuma das classes swing. Ao invés, ele envia mensagens para a Visão cuidar da real exibição e interação. Sendo assim, o Apresentador pode ser testado, novamente, somente com base em suas entradas vindas do Modelo e suas saídas para a Visão.
  • A Visão compreende a API da GUI. Ela não toma decisões de validação, seleção ou políticas. Ela tem praticamente inteligência zero. Ela simplesmente une a interface usada pelo Apresentador à API da GUI. Ela pode ser testada pela verificação das conexões. Os testes percorrem as estruturas de dados da GUI, certificando-se que os botões, campos de texto e caixas de verificação apropriados foram criados. Os testes enviam eventos para os widgets da GUI e certificam que as chamadas adequadas foram invocadas.

Como posso testar sistemas incorporados?

Alguns softwares são escritos para controlar o hardware. Você pode testar estes softwares escrevendo um simulador de hardware. Os testes definem vários estados no simulador de hardware e então conduzem o sistema para manipular o hardware. Finalmente, os testes consultam a simulação para assegurar que o hardware foi conduzido ao estado final correto.

Como posso testar concorrência?

Alguns softwares são re-entrantes ou concorrentes. Condições de concorrência podem tornar o comportamento do software indeterminístico. Existem algumas falhas que podem ser tanto severas quanto fortemente dependentes de temporização e ordem dos eventos. Softwares que funcionam 99,999% do tempo, podem falhar em 0,001% devido a problemas de concorrência. Encontrar estes problemas é um desafio.

Normalmente testes Monte Carlo exaustivos são usados para tentar direcionar o sistema através da maioria dos estados possíveis.

Quando os problemas de concorrência são descobertos, os testes podem ser escritos de forma que direcionem o sistema para o estado de falha e, depois, comprove a falha. Então, o problema pode ser reparado e o teste continuar na suíte de testes como um teste de regressão.

Como posso testar transações de banco de dados?

Quase sempre a melhor forma de fazer isso é criar uma interface que represente a base de dados. Cada caso de teste pode implementar essa interface e fingir ser a base de dados, fornecendo os seus próprios dados e interpretando as chamadas feitas pelo módulo em teste. Isso evita que os dados de teste sejam realmente escritos e lidos na base de dados. Isto também permite que o código de teste force condições de falha que de outra forma seriam difíceis de simular.

Veja: http://c2.com/cgi/wiki?MockObject

Como posso testar servlets?

Os servlets são simplesmente tubos através dos quais os dados do formulário passam para um programa e o HTML retorna. O truque para testar um servlet é separar o programa do tubo. Mantenha o código do servlet o menor possível. Coloque o seu programa nas velhas classes planas que não derivem de Servlets. Então você poderá testar estas velhas classes planas como de costume. Se o próprio servlet for bem pequeno, pode ser tão simples que não necessite de testes.

Certamente, você também poderá criar o seu próprio invocador de servlets ou usar uma versão de código aberto. Esses programas agem como servidores web e disparam os servlets para você. Você passa os dados do formulário para eles e eles retornam o HTML para você.

Veja:

http://c2.com/cgi/wiki?JunitServlet
http://c2.com/cgi/wiki?ServletTesting
http://strutstestcase.sourceforge.net/

Como posso testar páginas Web?

Um documento HTML é quase um documento XML. Existe uma ferramenta que permite que você pesquise um documento HTML como se fosse um documento XML. Esta ferramenta é chamada de HTTPUnit. Usando esta ferramenta, você poderá escrever testes que inspecionem o conteúdo de um documento HTML sem se preocupar com espaços em branco ou questões de formatação. Outra ferramenta chamada HTMLUnit também faz algo semelhante. A HTMLUnit inclui suporte para testar páginas HTML com JavaScript embutido.

Veja:

http://httpunit.sourceforge.net/
http://htmlunit.sourceforge.net/