Test-Driven Development (TDD), ou em português desenvolvimento orientado a testes é um processo de desenvolvimento onde os casos de teste são desenvolvidos em primeiro lugar. É um conjunto de técnicas de testes associada com Extreme Programming (XP) e pelas metodologias ágeis, sendo mesmo uma das unidades core do XP.
Existem inúmeras razões pelas quais se devem efetuar os testes primeiro lugar e só depois escrever o código do software.
Vantagens do uso de TDD
Algumas vantagens de se usar um desenvolvimento orientado a testes:
- O Código escrito é testável
- A equipa de desenvolvimento passa a obter um feedback do código desenvolvido de um modo mais rápido.
- Existe uma maior proximidade entre as especificações funcionais e o código desenvolvido. Ajuda a clarificar o comportamento. Obrigatoriedade de pensar antes de escrever o código.
- Devemos efetuar testes sobre o interface e não sobre a implementação.
- Os casos de testes definem exatamente o conjunto de funcionalidades necessárias tornando mais fácil identificar o código redundante, ou tarefas redundantes.
- A existência prévia de casos de testes fornece à equipa de desenvolvimento um modo fácil de criar um primeiro esboço ou protótipo do código pretendido. Do mesmo modo que facilita os refactors futuros. O conjunto de testes existentes funcionam como uma rede de segurança e garantem que ao efetuarmos um conjunto de alterações não alteramos o comportamento da aplicação e/ou não deixamos partes da mesma sem funcionar.
- A metodologia TDD encaixa muito bem num processo de desenvolvimento ágil. Esta metodologia acaba por levar ao desenvolvimento de código mais modularizado, flexível e mais extensível. Isto porque obriga a que se pense no código como unidades pequenas e independentes umas das outras.
- Como se pensa primeiro nos testes antes de escrever o código que vai para produção, os testes acabam por servir de guia ao desenvolvimento das funcionalidades embora estejam longe de ser uma especificação completa. Os testes como são escritos em primeiro lugar, ainda antes de o código estar escrito, devem portanto falhar. Ao escrevermos o teste devemos garantir que este falha e que falha pelo motivo correto. A utilização de mocks ou stubs pode ajudar a simular o código a ser escrito, assim como comportamentos corretos e incorretos dos funcionalidades a escrever. Obviamente estes mocks são apenas usados para simular o comportamento do código de produção enquanto este não existe de forma a ajudar na criação dos testes.
De modo a podermos usar TDD devemos efetuar uma comparação entre o custo de desenvolvimento do software segundo esta via com o correspondente ganho na qualidade do mesmo. Existem vários artigos (mesmo livros inteiros) alertando e evidenciando para os benefícios obtidos nos projetos que geralmente usam TDD. Um desses artigos pode ser encontrado aqui. (Link artigo “About the Return on Investment of Test-Driven Development”) Um segundo artigo, também interessante, que defende que o uso de TDD pode eliminar os custos de desenvolvimento em pelo menos 50% pode ser encontrado aqui.
Maioritariamente usam-se testes unitários como base principal do TDD dado à sua maior proximidade com o código desenvolvido. Mas não nos devemos limitar a este tipo de testes. Devemos continuar a escrever testes de verifiquem a correta integração dos mais diversos componentes. Podemos ainda ir mais longe e criar testes para os interfaces e para o modo como o utilizador interage com a aplicação. Para a criação dos testes de aceitação podemos usar frameworks como o jbehave ou o cucumber.
Algumas desvantagens e mitos sobre a utilização de TDD
Consegue-se criar uma bateria de testes de regressão que cubra 100% dos testes de unidade
Embora este até pareça um bom objectivo, infelizmente acaba por se tornar um pouco realística. Podem existir alguns componentes ou frameworks desenvolvidas por terceiros que não trazem uma suite de testes acoplada, muitas das vezes nem as próprias fontes do código são disponibilizadas. Nestes casos nunca iremos conseguir dar garantias que funcionaria a 100%. Embora se possam usar técnicas de caixa preta para validar o interface usado pela nossa aplicação com a aplicação externa nunca conseguiremos validar totalmente o sistema. Por outro lado validar um interface gráfico é uma tarefa bastante difícil, geralmente a quantidade de combinações possíveis de utilização é demasiado grande, senão mesmo infinita.
Podemos ainda estar a trabalhar com sistemas, ou que partes destes, tenham sido legados e que não existisse a prática de escrever testes nessa altura. Podemos ainda estar a lidar com equipas de desenvolvimento com pouco capacidade para criar e desenhar testes, podendo estes facilmente falhar partes importantes do sistema.
Os testes unitários formam 100% do design da especificação
Pessoas novas nesta metodologia, têm ou ficam por vezes com a percepção de que os testes unitários são suficientes para definir a especificação. È certo que os testes unitários definem uma parte da especificação de um produto. Assim como é certo que os testes de aceitação definem uma outra parte da especificação, mas estes elementos não são suficientes para formar uma especificação completa.
Apenas é necessário efetuar testes unitários
Em qualquer projeto de desenvolvimento, mesmos os mais simples, esta afirmação é claramente falsa. A comunidade agile é muito clara acerca da necessidade de outros tipos de testes. Os testes unitários baseiam-se em unidades simples do código não tendo em conta a sua interação com outros componentes. Pelo que muita coisa pode acontecer numa aplicação que usa vários serviços e bases de dados como suporte. Tom Fischer reflete isso muito bem no seu post sobre Unit Testing Myths And Practices.
A utilização de TDD é suficiente para os testes
TDD quando é apenas usada ao nível dos testes unitários, é como foi referido acima insuficiente. Mesmo quando estendemos os testes realizados sobre o paradigma de TDD a testes de aceitação, sobram muitos pontos de falha possíveis numa aplicação. Situações como segurança, usabilidade, performance, escalabilidade do software entre outras estão entre alguns dos pontos que não se encaixam diretamente sobre este paradigma.
A utilização de TDD têm problemas de escalabilidade.
Embora isto seja parcialmente verdade, pode ser facilmente ultrapassado. os problemas mais comuns são:
- Quando um projeto começa a ficar muito grande e a suite de testes começa a demorar demasiado tempo para correr, torna mais complicado correr builds frequentes se o tempo necessário para correr uma suite de testes for de uma hora, por exemplo. Por outro lado quanto mais tempo demorar o processo de execução dos testes mais tempo demoramos a receber feedback sobre o código produzido o que começa a contrariar um pouco os princípios de uma metodologia ágil que faz do rápido feedback um dos seus pontos fortes.
- Nem todos os elementos da equipa de desenvolvimento sabem como efetuar testes ou nem todos os elementos podem estar a usar uma abordagem TDD.
De uma forma bastante simplificada, o desenvolvimento usando TDD resume-se ao ciclo descrito no simplificado diagrama abaixo. Assim começamos sempre por “Criar um teste”. De seguida começamos por desenvolver o código necessário para passar o teste descrito. Após a conclusão do desenvolvimento deste basta correr os testes e verificar que estes passam. Se tudo correr bem podemos passar para a próximo ponto a desenvolver. Caso corra mal devemos corrigir os pontos errados e correr novamente os testes até que tudo fique OK.
Existem na web alguns recursos muito bons para começar a desenvolver a partir de TDD entre os quais os seguintes parecem-me bastante interessantes e fáceis de seguir.
O Seguinte conjunto de vídeos formam um excelente tutorial sobre o desenvolvimento de software utilizando a metodologia de Test Driven Development. São mais de oito horas de vídeos mas de fácil visualização e compreensão. È usado um exemplo de desenvolvimento utilizando a plataforma Ruby on Rails e com recurso aos seguintes utilitários “Rspec”, “Cucumber,”, “Selenium” ou Watir Webdriver. Embora sejam recursos construídos sobre a plataforma Ruby on Rails são bastante simples de perceber e acredito que facilmente adaptáveis a outras linguagens.
Efficient Rails Test-Driven Development - Part I
Efficient Rails Test-Driven Development - Part II
Efficient Rails Test-Driven Development - Part III
Efficient Rails Test-Driven Development - Part IV
Efficient Rails Test-Driven Development - Part V
Efficient Rails Test-Driven Development - Part VI
De seguida têm outro conjunto de recurso que penso que vale a penas dar uma vista de olhos.
- Misconceptions With Test Driven Development
- TDD Design Starter Kit
- Introduction to Test Driven Development (ou a correspondente tradução em brasileiro)
- Unit Testing Myths And Practices
Bom, até à próxima.