A algum tempo comecei a adotar a prática de TDD (Test-Driven Development). No início pensava "não tem como desenvolver desse jeito", em pouco tempo passei a pensar "não tem como desenvolver sem isso".
Existe muita informação na Internet a respeito de TDD, por isso não quero falar de como fazer, mas sim, dos conceitos e práticas importantes para quem está começando agora. Para quem está começando, parece que TDD é relacionado apenas a "testes automatizados", mas é muito mais que isso.
Definição de TDD
A prática de TDD se resume a três passos simples, mas fundamentais:
- Escrever um teste que falhe - o primeiro passo é pensar o que deseja-se fazer. Com isso, criar um teste definindo o contexto, a execução e as expectativas;
- Fazer o teste passar - deve-se escrever o mínimo de código necessário para fazer o teste passar, nada além disso;
- Refatorar - deve-se rever o código e verificar se há melhorias a serem feitas. Essas melhorias não podem alterar o comportamento apenas a estrutura do código.
Apesar dos passos serem simples, no início é difícil segui-los. Nossa tendência é escrever imediatamente todo o código e no final adicionar os testes. É justamente esse o ponto mais crítico e mais importante na adoção de TDD, se acostumar a escrever o teste primeiro. Toda a vantagem do desenvolvimento dirigido por testes vêm disso.
Podemos utilizar as três regras definidas por Robert Martin:
- Você não pode escrever nenhum código a não ser que ele seja necessário para fazer um teste que esteja falhando passar;
- Você não pode escrever nenhum teste além do necessário para falhar. E, falhas de compilação também são falhas;
- Você não pode escrever nenhum código além do necessário para fazer o teste passar;
Em resumo, só escrever qualquer código necessário para atender um teste que esteja falhando, nada além disso.
Benefícios
A grande pergunta é: quais os benefícios de TDD?
Para quem ainda não conhece a prática, a única vantagem vista são os testes unitários. Ninguém vê logo de cara os demais benefícios que essa prática traz. Alguns deles que posso destacar:
- Requisitos melhor detalhados - os testes ajudam a entender melhor os requisitos;
- Codificar apenas o necessário - como apenas é codificado o mínimo necessário para passar um teste evitamos introduzir códigos que não temos certeza se serão utilizados;
- Reduz o acoplamento das classes - para que os testes sejam fáceis de serem criados, as classes não podem ter um forte acoplamento;
- Deixa claro o comportamento do sistema - para um desenvolvedor que entra no projeto, ou quando vamos alterar um código existente, sempre podemos consultar os testes para saber qual o comportamento esperado;
- Incentiva a refatoração - é bastante comum ouvirmos a frase "é melhor não mexer ali pois não sabemos quais os problemas que pode gerar". Com um sistema com boa cobertura de testes, o desenvolvedor tem muito mais confiança para fazer qualquer alteração;
- Menor utilização do depurador - com os testes é muito mais fácil verificar o comportamento. Evitamos ter que abrir o sistema, acessar meia dúzia de telas e só depois testar a funcionalidade;
O que testar?
O ideal é que 100% do sistema esteja coberto por testes, porém isso dificilmente é possível. Há determinados pontos do sistema onde não é possível criar testes unitários ou o trabalho não compensa.
Normalmente o código que acessa recursos externos (banco de dados, sistema de arquivos, etc) não pode ser testado com testes unitários. Para estes casos é necessário criar testes integrados que, normalmente são mais difíceis de serem criados/mantidos e mais lentos. Deve-se tentar desacoplar ao máximo o sistema destes recursos externos.