Introdução do Desenvolvimento Dirigido por Testes (TDD)

por Scott W.Ambler traduzido por JEDeboni do original

O TDD ou desenvolvimento dirigido por testes (do inglês: Teste-drive design) Beck 2003 ; Astels 2003 ) é uma abordagem revolucionária de desenvolvimento que combina o “test-first development” onde se escreve o teste antes de se escreve o código apenas necessário para atender aquele teste com a refatoração. Qual é o objetivo principal do TDD? Uma visão é que o objetivo do TDD é de especificação e não de validação (Martin, Newkirk, and Kess 2003 ). Em outras palavras, é uma forma de pensar no seu projeto antes de escrever o código funcional. Uma outra visão é que o TDD é uma técnica de programação. Como Ron Jeffries gosta de dizer, o objetivo do TDD é escrever um código limpo que funcione. Eu acho que existe mérito nos dois argumentos, apesar que me inclino para a visão da especificação, mas deixo para você decidir.

Conteudo
1. O que é TDD?
2. TDD e o teste tradicional
3. TDD e documentação
4. Desenvolvimento de Banco de Dados Dirigido por Teste
5. Escalando o TDD via o Desenvolvimento Dirigido pelo Modelo Ágil (AMDD)
6. Por que o TDD?
7. Mitos e conceitos errados
8. Sumário
9. Ferramentas

1. O que é o TDD?

Os passos para o TFD – Test – First Design são vistos no diagrama de atividade da UML na figura 1. O primeiro passo é adicionar rapidamente um teste, basicamente uma parte do código vai falhar. A seguir se roda os testes, em geral toda o conjunto de testes apesar de que para ser mais rápido você pode decidir executar apenas um subconjunto, para garantir que o novo teste realmente falha. Você então atualiza a parte funcional do código para fazer ele passar nos novos testes. O quarto passo é executar os testes novamente. Se eles falharem você deve atualizar seu código funcional e testar novamente. Uma vez os testes tendo passado o próximo passo é começar novamente (você pode precisar refatorar qualquer duplicidade do seu projeto, transformando o TFD em TDD).

Esquema do TDD
Figura 1. Os passos do TFD.

Eu gostaria de descrever o TDD por meio de uma fórmula bem simples:

TDD = Refatoração + TFD

TDD muda completamente o desenvolvimento tradicional. Quando você vai implementar uma nova funcionalidade, a primeira questão que se pergunta é se o projeto existente é o melhor projeto possível que permita você implementar esta funcionalidade. Se for, você avança com a abordagem do TFD. Se não for, você refatora localmente para mudar a porção do projeto afetada pela nova funcionalidade, permitindo você adicioná-la o mais facilmente possível. Como um resultado você estará sempre melhorando a qualidade do projeto, consequentemente fazendo ele mais fácil de ser trabalhado no futuro.

Ao contrário de escrever um código funcional em primeiro lugar e depois o código de teste como um trabalho posterior, se é que você vai escrever os testes, você deve escrever o código dos testes antes do código do funcional. Além disso, você vai fazer isso em passo muito pequenos – um teste e um pouco do código funcional correspondente por vez. Um programador que adotar a abordagem do TDD recusa escrever uma nova função até que exista um código que falhe porque esta função não esta presente. Na verdade, eles se recusam a adicionar uma simples linha de código até que exista uma teste para ela. Uma vez o teste está no lugar então ele deve garantir que agora o conjunto de testes passam (o novo código pode quebrar alguns dos testes existentes assim como o novo teste). Isso parece um princípio simples, mas quando você esta iniciando na abordagem do TDD exige-se uma grande disciplina porque é fácil “escorregar” e escrever o código funcional sem escrever um novo teste.

Uma das vantagens da programação em pares é que o seu par pode ajudá-lo a se manter na linha. Uma das hipóteses assumidas pelo TDD é que você deve ter um ambiente de teste de unidades disponível para você. Os desenvolvedores ágeis de software geralmente usam uma ferramenta de código aberto da família XUnit, como o JUnit ou VBUnit, apesar de que existem opções em ferramentas comerciais disponíveis. Sem tais ferramentas o TDD é virtualmente impossível. A Figura 2 apresetna um diagrama de estados da UML sobre como as pessas trabalham, tipicamente, com as ferramentas xUnit. Este diagrama me foi sugerido por Keith Ray.


Figura 2. Testando com o Framework xUnit.

Kent Beck, que popularizou o TDD na programação extrema (XP) (Beck 2000 ), define duas regras simples para o TDD (Beck 2003 ). Em primeiro lugar, você deve escrever um novo código de negócio somente quando um teste automatizado falhar. Em segundo lugar, deve eliminar qualquer duplicidade que encontrar. Beck explica como estas duas regras simples geram um comportamento complexo em grupo e individualmente:

  • Você projeta organicamente, com um código executável oferecendo repostas entre as decisões.
  • Você escreve seus próprios teste porque você não pode esperar 20 vezes por dia que alguém faça isso para você.
  • Seu ambiente de desenvolvimento deve oferecer uma resposta rápida para pequenas mudanças (isto é, você precisa um compilador rápido e um conjunto de testes de regressão)
  • Seus projetos precisam ter componentes altamente coesivos, fracamente acoplados (por exemplo, seu projeto deve ser altamente normalizado) para fazer os testes mais simples (isto também faz com que a evolução e a manutenção do seu sistema mais fácil também).

Para desenvolvedores a implicação é que eles precisam aprender a escrever testes de unidade eficazes. A experiência de Beck é que bons testes:

  • Executem rápido (eles tem um rápidos setup, execução e finalização)
  • Executam isoladamente (deve-se ser capaz de reordená-los)
  • Usam dados que fazem eles fáceis de ler e entender
  • Usam dados reais (por exemplo, cópias de dados da produção) quando são necessários
  • Representam um passo na direção de um gol maior.

2. TDD e o teste tradicional

O TDD é, principalmente, uma técnica de projeto com um efeito colateral de garantir que o código fonte é completamente testado na unidade. Entretanto, existe mais teste a fazer do que isso. Você precisa ainda considerar outras técnicas que testes ágeis como o teste ágil de aceitação e o teste investigativo. Muito deste teste pode ser também realizado cedo no seu projeto se você escolher fazer assim (e você deveria). Na verdade, os testes de aceitação do XP para uma [estória do usuário] são especificados pelos stakeholders do projeto seja antes ou em paralelo à codificação, dando aos stakeholders a confiança que o sistema na realidade atende aos seus requisitos.

Com o teste tradicional um teste de sucesso é o que acha um ou mais defeitos. Acontece a mesma coisa com o TDD; quando um teste falha você esta fazendo um progresso porque é quando você precisa resolver o problema. Mais importante, você tem uma medida clara do sucesso quando o teste não falha mais. o TDD aumenta a sua confiança que o sistema verdadeiramente atende os requisitos definidos para ele, que o seu sistema funciona de verdade e portanto que você pode prosseguir com confiança.

Assim como com o teste tradicional, quanto maior o perfil de risco do sistema, mais completo seus testes deve ser. Com ambos os testes tradicionais e o TDD você não está buscando a perfeição, ao contrário, você está testando a importância do sistema. Parafraseando Agile Modeling (AM), deve-se testar com um propósito, e saber porque se está testando alguma soia e qual o nível que ela deve ser testada. Uma consequência interessante do TDD é que você consegue testar com 100% de cobertura – cada linha de código é testada – algo que o teste tradicional não garante (apesar de recomendar). Em geral, eu acho que é bem razoável dizer que apesar que o TDD é uma técnica de especificação, um valioso efeito é que resulta em um código significativamente melhor testado que as técnicas tradicionais.

Se vale a pena construir, vale a pena testar.
Se não vale a pena testar, porque você esta perdendo seu tempo com isso?

3. TDD e Documentação

Gostem ou não, a maioria dos programadores não leem documentação escrita para um sistema, ao contrário, eles preferem trabalhar com o código. E não há nada errado com isso. Quando tentam entender uma classe ou uma operação, a maioria dos programadores vão olhar, em primeiro lugar para a amostra de código que as envolve. Testes de unidade bem escritos fazem exatamente isso – oferecem uma especificação para o código funcional – e como resultado os testes de unidade passam a ser uma importante parte da sua documentação técnica. A implicação é que as expectativas da turma pró-documentação precisa refletir esta realidade. Analogamente, os testes de aceitação podem formar uma parte importante da documentação de requisitos. Isso faz muito sentido quando se para para pensar sobre isso. Seus testes de aceitação definem exatamente o que os seus stakeholders esperam do sistema, por isso eles especificam os requisitos críticos. Seu conjunto de testes de regressão, particularmente com a abordagem do test-first, efetivamente se tornam uma especificação detalhada executável.

Os testes podem ser uma documentação suficiente? Muito provavelmente não, mas eles formam uma parte importante dela. Por exemplo, você está prestes a descobrir que ainda precisa da documentação do usuário, uma visão geral do sistema, documentação operacional e de suporte. Você ainda pode até descobrir que precisa uma documentação resumida cobrindo todo o processo de negócio que o seu sistema suporta. Quando você aborda a documentação com a mente aberta, Eu suspeito que você vai concordar que estes dois tipos de teste cobrem a maioria das suas necessidades em documentação para desenvolvedores e stakeholders. Ainda mais eles são bons exemplos das práticas de gerencia ágil como a pratica do ponto único de informação e uma importante parte do esforço global para se manter o mais ágil possível apesar da documentação.

4. Desenvolvimento de Banco de Dados Dirigido por Testes

Enquanto este texto estava sendo escrito uma importante pergunta está sendo feita na comunidade ágil “o TDD pode ser usado para desenvolvimento de Bancos de Dados?” Quando se olha o processo descrito na Figura 1 é importante notar que nenhum dos passos especifica uma linguagem orientada a objetos como Java ou C#, apesar de que estes são os ambientes em que o TDD é tipicamente aplicado. Por que você não poderia escrever um teste antes de fazer uma mudança no seu esquema de banco de dados? Por que não se pode fazer uma mudança, executar os testes e refatorar seu esquema como requerido? Me parece que você só precisa escolher trabalhar assim.

Minha idéia é que os termos banco de dados TDD ou talvez TDDD (Test Driven Database Design), não vai funcionar tão bem quanto a aplicação TDD. O primeiro desafio é o suporte de ferramentas. Apesar de estarem disponíveis ferramentas para teste de unidade como o DBUnit , ainda existe uma tecnologia emergente enquanto este artigo esta sendo escrito. Alguns DBAs estão melhorando a qualidade dos testes que estão executando, mas ainda não vi ninguem adotando uma abordagem do TDD para o desenvolvimento de bancos de dados. Um desafio é que as ferramentas de teste de unidade ainda não são bem aceitas na comunidade de banco de dados, apesar de que isso está mudando, assim a minha expectativa é que nos próximos anos o TDD para banco de dados vai crescer. Em segundo lugar, o conceito de desenvolvimento evolucionário é novo para a maioria dos profissionais de banco de dadso – porque uma mentalidade serial ainda domina na comunidade tradicional e a maioria das ferramentas não suporta o desenvolvimento evolutivo. Minha esperança é que os fornecedores de ferramenta vão alcançar esta mudança de paradigma, mas minhas expectativas são que teremos que desenvolver ferramentas de código aberto. Em terceiro lugar, minha experiência é que a maioria das pessoas que trabalham orientadas a dados parecem preferir uma abordagem orientada a modelos e não orientada a testes. Uma causa disso parece que é porque uma abordagem orientada a testes ainda não foi amplamente considerada até agora, outra razão pode ser que os profissionais de bancos de dados são mais visuais e por isso preferem a abordagem orientada a modelos.

5. Escalando o TDD via AMDD (Desenvolvimento Ágil Dirigido a Modelos)

TDD é muito bom como uma especificação detalhada e validação, mas não muito boa para pensar em problemas maiores como uma visão geral do projeto, como as pessoas vão usar o sistema, ou o projeto da interface (por exemplo). Modelagem, ou ou o AMDD (Desenvolvimento Ágil Dirigido a Modelos) (o ciclo de vida é apresentado na figura 3) é mais adaptada para isso. O AMDD atende o problema de escalar os problemas do TDD não resolve.

Figura 3. O ciclo de vida do AMDD (Desenvolvimento Ágil Dirigido a Modelos)

Comparando o TDD e o AMDD:

  • O TDD abrevia a realimentação da programação enquanto o AMSS abrevia a realimentação da modelagem.
  • O TDD provê uma especificação detalhada (testes) enquanto o AMDD é melhor para pensar os grandes problemas.
  • O TDD promove o desenvolvimento de software de alta qualidade enquanto o AMDD promove uma comunicação de alta qualidade com os stakeholders e outros desenvolvedores.
  • O TDD fala com os programadores enquanto o AMDD fala com os analistas de negócio, stakeholders e profissionais de dados
  • O TDD oferece uma realimentação concreta muito fina e da ordem de minutos enquanto o AMDD permite uma realimentação verbal da ordem de minutos (uma realimentação concreta requer que os desenvolvedores sigam a prática de provar com o código e então se tornam dependentes de técnicas não AM)
  • O TDD é orientada não-visual enquanto a AMDD é orientada visualmente.
  • Ambas as técnicas são novas para os desenvolvedores tradicionais e por isso podem ser uma ameaça para eles.
  • Ambas as técnicas suportam o desenvolvimento evolutivo.

Que abordagem eu demo adotar? A resposta depende das suas preferencias cognitivas e de seus colegas. Algumas pessoas são primeiramente um “pensador visual”, também chamado de pensadores espaciais.

6. Porque TDD?

Uma vantagem significativa do TDD é que ele permite que você tome pequenos passos quando estiver escrevendo um software. Esta prática que eu tenho promovido por anos porque é muito mais produtivo que tentar codificar em passos maiores. Por exemplo, assuma que você adicionou um código funcional, compilou e testou. A probabilidade é boa que os testes irão ser quebrados por defeitos que existem no seu código novo. É muito mais fácil encontrar, e corrigir, estes defeitos se você tiver escrito duas linhas de código do que duzentas. A consequência é que quanto mais rápido o seu compilador e conjunto de teste, mais atrativo é prosseguir em passos mais e mais pequenos. Eu geralmente prefiro adicionar poucas linhas de código no código funcional, tipicamente menos de dez, antes de recompilar e rodar os testes.

Eu acho que Bob Martin confirma que “o ato de escrever um teste de unidade é mais um ato de projeto do que de verificação. Ele também é mais um ato de documentação do que verificação. O ato de escrever um teste de unidade fecha um grande número de laços de realimentação, no mínimo aqueles que se procura na verificação de uma função.”

A primeira reação que muitas pessoas tem das técnicas ágeis é que elas funcionam bem em projetos pequenos, talvez envolvendo um pequeno número de pessoas por poucos meses, mas que eles não vão funcionar em projetos “reais” que são muito maiores. Isto simplesmente não é verdade. Beck (2003) relata trabalhar em um sistema Smalltalk completamente com a abordagem dirigida a testes que levou 4 anos, 40 pessoas-ano de esforço, resultando em 250000 linhas de código funcional e 250000 linhas de código de teste. Eram 4000 testes sendo executados em menos de 20 minutos, com o conjunto completo de testes sendo executada várias vezes por dia. Apesar de que existem muitos sistemas grandes, eu pessoalmente trabalhei em sistemas com muitas centenas de pessoas-ano de esforço e está claro que o TDD funciona para sistemas de bom tamanho.

Figura 4. Visão geral de teste para as equipes ágeis.

8. Sumário

O Desenvolvimento Dirigido por Testes (TDD) é uma técnica de desenvolvimento onde você deve escrever primeiro os testes que falham antes de você escrever um novo código funcional. O TDD está sendo adotado rapidamente pelos desenvolvedores de software para desenvolvimento uma aplicação de código fonte e está sendo até adotada pelos DBAs ágeis para o desenvolvimento de bancos de dados. TDD deve ser visto como complementar a abordagem do AMDD (Desenvolvimento Ágil Dirigido por Modelos) e os dois podem e devem ser usados em conjunto. O TDD não substitui TDD does not replace traditional testing, instead it defines a proven way to ensure effective unit testing. A side effect of TDD is that the resulting tests are working examples for invoking the code, thereby providing a working specification for the code. My experience is that TDD works incredibly well in practice and it is something that all software developers should consider adopting.

Figura 5. Como as equipes ágeis validam o seu trabalho.

9. Ferramentas

    10. Referências e sugestões de leitura on-line

    Comments are closed.