Desenvolvimento Orientado a Testes
O desenvolvimento orientado a testes (TDD, na sigla em inglês) é um caso especial de programação orientada a testes que adiciona o elemento de design contínuo.
A programação orientada a testes envolve a criação de testes unitários automatizados para o código de produção. antes Você escreve esse código de produção. Em vez de escrever testes posteriormente (ou, mais tipicamente, nunca escrever esses testes), você sempre começa com um teste unitário. Para cada pequeno trecho de funcionalidade no código de produção, você primeiro cria e executa um pequeno (idealmente muito pequeno) teste focado que especifica e valida o que o código fará. Esse teste pode até não compilar inicialmente, porque nem todas as classes e métodos que ele requer podem existir. Mesmo assim, ele funciona como uma espécie de especificação executável. Em seguida, você o compila com o mínimo de código de produção possível, para que possa executá-lo e observar sua falha. (Às vezes, você espera que ele falhe, e ele passa, o que é uma informação útil.) Então, você produz exatamente a quantidade de código necessária para que esse teste seja aprovado.
Essa técnica parece estranha, a princípio, para muitos programadores que a experimentam. É um pouco como alpinistas subindo uma parede rochosa aos poucos, colocando âncoras à medida que avançam. Por que se dar a todo esse trabalho? Certamente isso os atrasa consideravelmente, não é? A resposta é que só faz sentido se você acabar dependendo muito e repetidamente desses testes unitários posteriormente. Aqueles que praticam o desenvolvimento orientado a testes (TDD) regularmente afirmam que esses testes unitários compensam amplamente o esforço investido em escrevê-los.
Para desenvolvimento orientado a testes (TDD), você normalmente usará um dos frameworks de teste unitário automatizados da família xUnit (JUnit para Java, NUnit para C#, etc.). Esses frameworks facilitam bastante a criação, execução, organização e gerenciamento de grandes conjuntos de testes unitários. (No mundo Java, pelo menos, eles estão cada vez mais integrados às melhores IDEs.) Isso é bom, porque, ao desenvolver com TDD, você acumula muitos e muitos testes unitários.
Benefícios do trabalho baseado em testes
Conjuntos completos de testes unitários automatizados funcionam como uma espécie de rede para detecção de bugs. Eles definem, de forma precisa e determinística, o comportamento atual do sistema. Boas equipes que priorizam testes (TDD) constatam que encontram substancialmente menos defeitos ao longo do ciclo de vida do sistema e gastam muito menos tempo depurando. Testes unitários bem escritos também servem como uma excelente documentação de design que está sempre, por definição, sincronizada com o código de produção. Um benefício um tanto inesperado: muitos programadores relatam que a "pequena barra verde" que mostra que todos os testes estão sendo executados sem erros se torna viciante. Uma vez que você se acostuma com esses pequenos e frequentes feedbacks positivos sobre a saúde do seu código, é muito difícil abrir mão disso. Finalmente, se o comportamento do seu código for definido com muitos bons testes unitários, é muito mais fácil de executar. safer para você refatorar o códigoSe uma refatoração (ou um ajuste de desempenho, ou qualquer outra alteração) introduzir um bug, seus testes o alertarão rapidamente.
Desenvolvimento orientado a testes: levando-o adiante
O desenvolvimento orientado a testes (TDD) é um caso especial de programação orientada a testes que adiciona o elemento de design contínuo. Com o TDD, o design do sistema não é limitado por um documento de design em papel. Em vez disso, você permite que o processo de escrita de testes e código de produção guie o design à medida que avança. A cada poucos minutos, você refatora para simplificar e esclarecer. Se você consegue imaginar facilmente um método, classe ou modelo de objeto inteiro mais claro e conciso, você refatora nessa direção, protegido o tempo todo por um conjunto robusto de testes unitários. A premissa por trás do TDD é que você não pode realmente saber qual design lhe servirá melhor até que esteja imerso no código. À medida que você aprende sobre o que realmente funciona e o que não funciona, você está na melhor posição possível para aplicar esses insights, enquanto eles ainda estão frescos na sua mente. E toda essa atividade é protegida por seus conjuntos de testes unitários automatizados.
Você pode começar com uma boa quantidade de planejamento inicial, embora seja mais comum começar com bastante planejamento prévio. design de código simplesEm contextos de programação extrema, alguns esboços UML em um quadro branco costumam ser suficientes. Mas, com TDD, o quanto você define o design inicial importa menos do que o quanto você permite que esse design se desvie do ponto de partida ao longo do processo. Você pode não fazer mudanças arquitetônicas drásticas, mas pode refatorar o modelo de objetos em grande medida, se isso parecer a opção mais sensata. Algumas empresas têm mais liberdade política para implementar TDD de forma eficaz do que outras.
Testes em primeiro lugar versus depuração
É útil comparar o esforço gasto escrevendo testes antecipadamente com o tempo gasto depurando. A depuração geralmente envolve analisar grandes quantidades de código. O desenvolvimento orientado a testes permite que você se concentre em um bloco menor, no qual menos coisas podem dar errado. É difícil para os gerentes preverem quanto tempo a depuração realmente levará. E, de certa forma, muito esforço de depuração é desperdiçado. A depuração envolve investimento de tempo, estrutura e infraestrutura (pontos de interrupção, monitoramento de variáveis temporárias, instruções de impressão) que são essencialmente descartáveis. Depois de encontrar e corrigir o bug, toda essa análise é essencialmente perdida. E se não for perdida completamente para você, certamente será perdida para outros programadores que mantêm ou estendem esse código. Com o desenvolvimento orientado a testes, os testes estão disponíveis para todos, para sempre. Se um bug reaparecer de alguma forma, o mesmo teste que o detectou uma vez poderá detectá-lo novamente. Se um bug surgir porque não há um teste correspondente, você pode escrever um teste que o capture a partir de então. Dessa forma, muitos profissionais que adotam a abordagem "test-first" afirmam que ela representa o ápice do trabalho inteligente em vez do trabalho árduo.
Técnicas e ferramentas de teste primeiro
Nem sempre é trivial escrever um teste unitário para cada aspecto do comportamento de um sistema. E quanto às GUIs? E quanto aos EJBs e outras estruturas gerenciadas por frameworks baseados em contêineres? E quanto aos bancos de dados e à persistência em geral? Como testar se uma exceção é lançada corretamente? Como testar os níveis de desempenho? Como medir a cobertura, a granularidade e a qualidade dos testes? Essas questões estão sendo respondidas pela comunidade de desenvolvimento orientado a testes (TDD) com um conjunto de ferramentas e técnicas em constante evolução. Uma enorme engenhosidade continua a ser investida para tornar possível cobrir todos os aspectos do comportamento de um sistema com testes unitários. Por exemplo, muitas vezes faz sentido testar um componente de um sistema isoladamente de seus colaboradores e recursos externos, usando objetos falsos e mocks. Sem esses mocks ou objetos falsos, seus testes unitários podem não conseguir instanciar o objeto em teste. Ou, no caso de recursos externos como conexões de rede, bancos de dados ou GUIs, o uso do recurso real em um teste pode torná-lo extremamente lento, enquanto o uso de uma versão falsa ou mock mantém tudo funcionando rapidamente na memória. E embora alguns aspectos da funcionalidade possam sempre exigir teste manual, a percentagem em que isso é indiscutivelmente verdade continua a diminuir.