Olá, pessoal! Hoje eu quero compartilhar com vocês minha experiência com TDD: como eu o uso na prática, de forma mais coorporativa e em uma situação na qual eu tinha que entregar algo de fato. Essa foi a minha primeira experiência e, como em toda primeira vez, a gente nunca sabe direito qual vai ser o resultado. Podemos falhar, ou alcançar um nível razoável de satisfação, que vai melhorar com o tempo.
E para sabermos disso, precisamos apenas de coragem e de deixar o medo de errar de lado. Foi assim que dei meus primeiros passos em TDD. Algo que aprendi de cara é que uma coisa é usar o TDD em seus projetos pessoais, em casa; e outra é quando lidar com a complexidade do mundo dos negócios. São experiências muito distantes, porém, se não tivesse feito a experiência caseira, o impacto seria maior.
Então, vamos lá: vou compartilhar como aprendi TDD de modo prático e tentar fazer com que minha experiência te motive a usar o TDD, pois ainda há muitos programadores que não conseguem entender o uso, ou como é possível fazer isso no dia-a-dia. É claro que há exceções, nas quais o TDD não se aplica, porém a metodologia já comprovou que ele pode ser aplicado com eficiência, trazendo os resultados indiscutíveis!
Cenário
Surgiu uma nova feature em um projeto e por acaso caiu na minha mesa. Desde o escopo, design, desenvolvimento etc. É responsabilidade nossa, os engenheiros, encontrar uma solução para a tal feature, pois os arquitetos querem ela pronta no prazo X, dentro dos critérios de aceitação Y. A feature tinha que delegar trabalhos para as classes que fossem responsáveis por um determinado trabalho, porém há vários trabalhos, como popular banco e saber qual a melhor performance para buscar uma determinada informação. É preciso saber onde há um plano B de buscas quando um serviço primário está off, além disso, é preciso garantir que aquela informação estará no serviço B. Essa é uma feature um pouco complexa, por ser uma solução em cloud, e sobre a qual eu não posso dar muitos detalhes.
Minha experiência
Como havia muitas features para o produto, no início surgiu uma dúvida: por onde começar? Qual a melhor feature? Eu selecionei a mais simples. Poderia ir logo na que aparentava ser a mais difícil, mas com TDD eu precisava fazer de forma mais simples possível, saindo do vermelho para o verde. Então, eu comecei a criar os units tests para uma determinada classe, pensando nos problemas, principalmente naqueles esperados, e nas exceções que deveriam acontecer quando o contexto fosse ZX.
E foi assim que comecei: da maneira mais simples e “boba”. À medida que fui adicionando novas features e o código foi mudando, os testes mais simples falhavam – e isso era bom, porque eu sabia que uma nova feature, que havia sido implementada, fez meu código quebrar e eu fiquei sabendo disso antes e podia consertar o erro antes de seguir adiante. Esses feedbacks rápidos foram cruciais, pois não esperei até o deploy acontecer e o pessoal de QA abrir um bug.
Para saber o que precisava ser feito, fiz um quadro com as tasks, o que está sendo feito, e o que foi feito (faltou uma coluna com o blocker). Assim, eu pude manter o foco e saber de fato o que estava fazendo, e isso ajuda até dar um bom status nas scrums com o time.
Resultado
O resultado foi além do esperado: no início, eu gastei bastante tempo com o units tests, seguindo os conceitos da técnica de TDD, porém ganhei mais velocidade lá na frente e principalmente na semana final, pois tínhamos um código limpo (o TDD ajudou identificar onde deveria refatorar), os units tests fizeram uma boa cobertura e testes rápidos.
Moral da história: ganhei dois dias livres, pois conseguir entregar antes do deadline, com um bom tempo sobrando para fazer qualquer coisa. E o melhor de tudo: com a segurança de que os critérios de aceitação daquela feature estavam sendo alcançados com o menor esforço possível (não foi necessário dar aquelas fazer hora extra!). Então, “feature accepted” com boa cobertura de unidade e code clear. Tudo possibilitado por algo chamado TDD. Observem que nada desses benefícios indiretos foram planejados, vieram como consequência da técnica de TDD.
O TDD não é a solução para todos os problemas de desenvolvimento de software. É apenas uma técnica, e como tal depende de como está sendo aplicada no projeto. Se as pessoas envolvidas não acreditam, já entramos perdendo no jogo. Eu faço uma associação bem abstrata para quem não entende TDD com aquelas metodologias que aprendemos na faculdade de como fazer o seu TCC: sabemos que só a metodologia não vai fazer o seu TCC, você vai precisar entender do assunto que deseja escrever. A metodologia vai te guiar e auxiliar a terminar aquilo da melhor forma possível: saber quanto falta para acabar e quando finalmente chegou ao final. E inclusive se vai dar tempo de entregar no prazo! O TDD te mostra isso!
Um livro que eu deveria ter lido, antes de ir para minha primeira experiência, era o do Kent Beck, mas não tive tempo, pois foi um desafio que surgiu em questão de poucos dias e eu tive que usar os materiais que já tinha em mãos. Então, li bastante artigos de pessoas mais experientes no assunto: Guilherme Silveira, Philip Calçado, Rodrigo Yoshima etc. E, o que não deu pra ver ler, teve que ser visto na prática com erros e acertos; sentindo na pele.
Agora que já compartilhei como foi a minha primeira experiência, vamos ver uma maneira simples para praticar.
O exemplo
O que vou usar como exemplo é o algo muito comum e o escolhi porque marcou minha vida, pois foi através dele que consegui dar meus primeiros passos no desenvolvimento guiado por testes. Não é nada chique! Desde da faculdade, o que mais vemos é algo como RG, CPF, Locadora etc. Porém, o objetivo aqui não é saber se você sabe validar um RG, CPF, CNPJ etc. E sim se você consegue desenvolver usando TDD.
Não esquecer!
O TDD não é apenas criar unit tests e sim criar testes inteligentes, ou seja, automatizados o suficiente para avisar quando algo quebrar alguma regra. Outro detalhe: não é por serem unit tests que você escreve de qualquer forma. É code, então os princípios são os mesmos utilizados no código funcional (não sei por que tem gente que separa isso. Até hoje não entendi o motivo). Na verdade, quem desenvolve utilizando TDD tem um carinho enorme com seus testes, pois sabe da importância deles durante o desenvolvimento e manutenção. Já vi perguntas do tipo: por que não coloca tudo em um teste só? (será que precisa responder o motivo?). Ou coisas como “o que é um bom teste?” (essa foi uma boa pergunta!).
Primeiro: não há receita de bolo, mas um bom teste sempre tem um perfil parecido, avisa de forma eficiente, ou seja, não faz alarde por qualquer coisa que mudou, só quando algo que ele de fato testa mudou. Qual a diferença? Muitas! Existem testes que têm várias responsabilidades. Dá para perceber isso pelo tempo de execução e o tamanho do método, por exemplo. Então, se algo mudou, ele falha. E essa própria falha daria um novo teste especifico, mas o desenvolvedor que só escreve teste por escrever não se importa com isso. O fato é que o teste está lá.
Enfim, um bom teste deve ser especifico e rápido, assim você pode executá-lo várias vezes.
Praticando
No exemplo a seguir, temos uma aplicação que faz a “validação” de um número de uma carteira de identidade. Ele é um start-up para você começar a brincar, mas terá que ser persistente e ir mais adiante para olhar o que tem através do muro.
Primeiro passo
Escrevemos a classe de teste e já fazemos o teste para os possíveis RG inválido/válido.
public class RGTest extends TestCase {
private RG validarg = new RG();
public void testIsValidaRG(){
assertFalse("retorna FALSE - inválido RG", validarg.isValidaRG("128641011"));
assertFalse("retorna FALSE - RG inválido", validarg.isValidaRG(null));
assertFalse("retorna FALSE - RG inválido", validarg.isValidaRG(""));
assertTrue("retorna TRUE - RG válido", validarg.isValidaRG("128640-28"));
Se for executado, ele vai falhar porque não temos a classe principal “RG”, que é a que nós precisamos implementar. A falha anterior é de compilação, caso você execute.
public class RG {
public boolean isValidaRG(String rg){
return false;
}
Segundo passo
Agora precisamos testar e ver se vai falhar – pela teoria, deve falhar.

O motivo da falha é que o método isValidaRG(String rg) sempre retorna false, então, nosso teste não está funcionando 100%. Afinal, precisamos ter um número de RG válido.
Terceiro passo
Para fazer o teste funcionar, precisamos ter um RG válido; e, para isso, precisamos alterar o método da classe principal.
public boolean isValidaRG(String rg){
if((rg== null) || (rg.length()!=11)|| (rg.isEmpty()) || rg.charAt(8)!= '-'){
return false;
}
return true;}
Agora estamos validando os nossos assertXXX da classe teste.

O bug
O código abaixo passa no teste. Observe que não há nenhuma regra validando letras e sabemos que não existe RG com letras. Precisamos criar um teste para isso e resolver o bug.
assertTrue(validarg.isValidaRG("abcdefgh-ij"));
Quarto passo
Criamos um método na classe RGTeste para testar se, de fato, nossa aplicação não aceita letras. Mas, pelo resultado abaixo, vimos que aceita, uma vez que o método retorna TRUE e que não há nada que proíba as letras na classe principal.
//criando um novo método para testar letras
public void testIsValidaRGLetras(){
assertFalse("retorna FALSE - inválido letras RG", validarg.isValidaRG("ABCDEFGH-IJ"));
assertFalse("retorna FALSE - Inválido letras RG", validarg.isValidaRG("G3X8Xopa-22"));}

Quinto passo
Precisamos agora alterar isso no código principal, para testar e ver se letras não podem passar pelo método.

public boolean isValidaRG(String rg){
if((rg== null) || (rg.length()!=11)|| (rg.isEmpty()) || rg.charAt(8)!= '-'){
return false;
}//fim do if
for (int i = 0; i < rg.length(); i++) {
if(i!=8){
char posicao = rg.charAt(i);
//se nao for um digito retorne false
if(!Character.isDigit(posicao)){
return false;
}
}
}
return true;
Os dois testes passaram e assim sabemos que a validação de RG está correta – até agora. Porém, o que devemos observar que nessa mecânica é que fomos escrevendo o nosso código com base nos resultados dos unit tests, e não fazer o inverso. É obvio que ainda falta muito trabalho até termos um RG realmente válido e inválido, mas lembre-se de que o básico do TDD é querer fazer a barra verde chegar o quanto antes e com o menor esforço possível.
É comum queremos implementar logo a parte complexa do código e fazer as validações possíveis. Mas é aí que o terreno está propício para nascer os bugs mais terríveis de nossa vida e só saberemos quando o cliente for abrir. Encontrar bugs em modelo como waterfall não é uma tarefa fácil, não é à toa que o custo de manutenção nesse modelo é alto. Lembre-se de que devemos desenvolver algo que qualquer outro desenvolvedor possa entender no menor tempo possível e o TDD possibilita isso. Eu, particularmente, quando pego um código feito com TDD olho primeiros os testes. Os testes sãos os guias que precisamos.
Quando iniciei meus estudos, nunca achei que o TDD fosse vantagem (acho que você deve estar com esse sentimento também), quando eu olhava apenas a execução e o resultado. Mas confesso que só conseguir ver a essência do TDD em partes diferente do projeto: uma quando precisamos alterar algo, e manter os testes passando, ou quando recebia novos requisitos e tinha que implementar e criar mais testes. Nesses momentos eu vi a agilidade que o TDD proporciona na prática e fui ficando viciado – sem falar que todo time fica feliz pelo tempo economizado na manutenção. O TDD, de fato, dá uma boa contribuição na manutenção de software.
5 Comentários
Qual a sua opinião?