Um dos grandes desafios ainda enfrentados pelos times de desenvolvimento de software é a garantia da qualidade. Em projetos que utilizavam o método tradicional Cascata, tínhamos uma fase bem definida onde os testes do software eram feitos. O problema é que essa era uma fase tardia e muitas vezes atropelada ou ignorada por causa da pressão exercida pelo prazo de entrega.
Neste artigo vamos falar sobre Test Driven Development (TDD), que é uma prática de desenvolvimento de software na qual os testes são pensados e escritos antes do código de negócio. Com isto, aumentamos muito a qualidade do produto e consequentemente a satisfação de nossos clientes e usuários.
Tem como testar no início, antes de começar o desenvolvimento?
Sim, a qualidade do software é responsabilidade de todos. Antes mesmo de começar o desenvolvimento. Product Owner, Time de Desenvolvimento e Scrum Master devem trabalhar juntos para garantir que isto aconteça.
Para os próximos tópicos, utilizaremos como exemplo um Time que desenvolve um site de e-commerce. Durante uma reunião, o diretor de marketing deu a seguinte notícia: no dia 15 deste mês, lançaremos a promoção de natal na qual ofereceremos descontos da seguinte forma: compras menores do que R$ 500,00 permanecem como são hoje, não tem desconto. Compras acima de R$ 500,00 ganham um desconto de 10%. O Product Owner escreveu então a seguinte história de usuário: Eu, enquanto comprador, desejo ganhar desconto para economizar meu 13º nas compras de natal.
Critérios de Aceitação
Para cada item do backlog, o Time deve definir os critérios de aceitação. Esses critérios definem o que o comportamento do software esperado pelo Product Owner, usuários, clientes ou demais stakeholders. Os critérios devem ser definidos antes do início do desenvolvimento para evitar surpresas no final e as famosas frases que geram embates entre o Time de Desenvolvimento e Product Owner: “Você não falou isso antes”, “Isso nós não sabíamos”, “Vocês não implementaram tal coisa”, etc.
Um formato interessante de escrita dos critérios de aceitação foi apresentado por Dan North em 2006. Ele resolve algumas coisas importantes para um bom teste. Qual o cenário em que o usuário está, qual a ação desse usuário e o qual o resultado ele receberá. O formato é:
Dado que <Condições necessárias>
Quando <evento>
Então <resultado>
Os critérios de aceitação para a história que estamos tratando poderiam são:
Critério de Aceitação 01
Dado que o cliente fez uma compra de R$ 499,99
Quando fechar a compra
Então não recebe desconto E o valor final da compra é R$ 499,99.
Critério de Aceitação 02
Dado que o cliente fez uma compra de R$ 500,00
Quando fechar a compra
Então recebe um desconto de 10% E o valor final da compra passa a ser R$ 450,00.
Perceba que os critérios descrevem com clareza para o time o que é o desconto escrito na história de usuário. Neste caso, eles também ajudam a definir o valor limite da faixa de desconto.
Test Driven Development (TDD)
Para demonstrar o funcionamento do TDD vamos criar um projeto Java simples utilizando a IDE Eclipse. Lembrando que TDD não é específico do Java e funciona em diversas linguagens de programação.
Preparando
Para criar um projeto Java vá em File → New → Java Project. Para este exemplo criamos um projeto chamado e-commerce.
Para fazer os TDD, você também terá que acrescentar o framework JUnit no seu Classpath clicando com o botão direito em cima do seu projeto → Build Path → Add Libraries → JUnit → Escolha a versão 4 ou 5.
Vamos montar a seguinte estrutura do projeto.
Separando as coisas
Não misture código de teste com código de negócio. O código de testes não irá para produção. Estamos separando as classes de negócio no source folder src/main/java e as classes de testes em src/test/java.
No nosso exemplo, durante o planejamento, o time acordou que a funcionalidade que resolve a história seria implementada na classe CalculadoraDescontos. Eles vão criar o método chamado calcular que recebe o valor da compra e retorna o valor com desconto. Todavia, como nosso time usa TDD, a nossa primeira criação será a classe CalculadoraDescontosTest no pacote br.com.k21.e-commerce.control.test.
Vamos criar o primeiro teste com o primeiro critério de aceitação.
Antes de prosseguirmos, há alguns pontos importantes que devemos tratar.
Nomes significativos
A não ser que você esteja trabalhando em uma linguagem com restrições no tamanho do nome de métodos ou variáveis como Cobol, não economize letras ao fazer suas declarações. Quando outro programador bater o olho no seu teste, ele deve entender o que o teste faz, quais são e para que servem as variáveis de entrada e saída e qual método foi chamado.
O código de testes deve ser muito simples para que ele se torne a documentação. Se você está tendo que colocar comentários nos testes, comece a pensar se o código de negócio não está muito acoplado ou a complexidade muito alta.
“Sempre programe como se o cara que vai manter o seu código fosse um psicopata violento que sabe onde você mora”
James F. Woods, 1991
Arrange Act Assert (AAA)
A estrutura de um bom teste é composta de 3 partes bem definidas conhecidas como Arrange, Act e Assert (AAA).
O Arrange é a parte de arranjo, preparação do teste. Aqui colocamos as variáveis que vamos precisar no teste ou qualquer linha de código necessária para que a ação que queremos testar seja executada.
O Act é a ação. Aqui chamamos o método que queremos testar na classe de negócio.
Assert é a afirmação. Aqui verificamos se o valor esperado é ou não idêntico ao valor calculado.
Baby Steps
No TDD também gostamos de Baby Steps (passos de bebês). Se você é um programador e viu os nossos critérios de aceitação, provavelmente já pensou em todas as condições necessárias para atender aos critérios. Sua vontade agora é já colocar os IFs na classe de negócio. NÃO FAÇA ISSO!
Vá com calma. Em contextos reais, você terá muito mais do que 3 critérios de aceitação. Colocar tudo na classe de negócio, de uma só vez, fará com que você perca a noção do que já foi testado ou não. Ao fazer os testes depois, você poderá acabar com um falso-positivo. Alguma coisa que não está funcionando corretamente, mas você não conseguiu cobrir as condições com um teste.
Como você pode ver, a classe de negócio não foi criada e por isso o Eclipse está acusando um erro na linha 13. Se você rodar os testes (Clique com o botão direito sobre o projeto → Run As → JUnit Test), receberá um erro: java.lang.Error: Unresolved compilation problems: CalculadoraDescontos cannot be resolved to a type. Que significa que a CalculadoraDescontos não existe.
Vamos criá-la. Clique com o botão direito no pacote br.com.k21.e-commerce.control no source folder src/main/java → New → Class → Name: CalculadoraDescontos → Finish.
Retorne à classe CalculadoraDescontosTest e importe a CalculadoraDescontos. Repare que agora o erro mudou para a linha 21. Se você rodar os testes novamente, receberá o erro: java.lang.Error: Unresolved compilation problem: The method calcular(double) is undefined for the type CalculadoraDescontos. Que significa que o método calcular ainda não existe na calculadora.
Vamos criar o método calcular na CalculadoraDescontos. Inicialmente ele está assim:
O Ciclo de TDD: Red, Green, Refactor
Red (Vermelho) significa que a primeira vez que rodamos o teste, ele não pode passar. Tem que dar erro. Isso irá ajudá-lo a evitar os falsos-positivos.
Green (Verde). Você irá implementar o código de negócio, apenas o suficiente para que o teste passe. Nada de fazer coisas rebuscadas no início.
Refactor (Refatorar). Os passos vermelho e verde criam uma espécie de tela de proteção que garantem que o seu código não tem comportamentos inesperados. A partir daqui podemos refatorá-lo para deixá-lo limpo.
Rode o teste e você receberá um erro: java.lang.AssertionError: expected:<499.99> but was:<-1.00>. Que significa que esperávamos um valor e recebemos outro. O erro está na linha 24 (Assert).
Vamos implementar o mínimo que fará o nosso teste passar.
Simplesmente estamos retornando o valor da compra passado como o valor após o desconto. Rode os testes e eles devem ficar verdes.
É necessário fazer o Refactoring? Até o momento não. Podemos ir para o próximo critério de aceitação.
Critério de Aceitação 02
Dado que o cliente fez uma compra de R$ 500,00
Quando fechar a compra
Então recebe um desconto de 10% E o valor final da compra passa a ser R$ 450,00.
Perceba que agora já temos que ter uma condição. Se a compra for de R$ 500,00, então o método calcular deverá retornar R$ 450,00. Vamos criar o teste:
Repare que eu mantenho todos os testes que eu já criei. E que eles são mais ou menos um Copia e Cola um do outro. Ao rodar os testes você receberá 1 verde do teste anterior e 1 vermelho do teste que não está passando e tudo ficará vermelho.
Vamos implementar mais uma vez o mínimo para a coisa funcionar.
Rode os testes. Vamos ver que agora está tudo verdinho.
1, 2, N
O que acontece se o valor da compra for de R$600,00. Vai passar? Crie um teste para isso.
Rode os testes. Não passou? Por quê?
Vamos usar o padrão 1, 2, N. Isso significa que vamos mexer no código apenas o suficiente para o teste passar da primeira vez. Apenas o suficiente para passar a segunda vez. Daí em diante, criamos um padrão e podemos dar uma solução mais concreta para todos os casos.
Vamos então implementar apenas o suficiente para esse teste passar e manter os demais funcionando.
Vamos usar o padrão 1, 2, N. Isso significa que vamos mexer no código apenas o suficiente para o teste passar da primeira vez. Apenas o suficiente para passar a segunda vez. Daí em diante criamos um padrão e podemos dar uma solução mais concreta para todos os casos.
Vamos então implementar apenas o suficiente para esse teste passar e manter os demais funcionando.
Rode os testes e veja que estará tudo verdinho.
Agora, e se o valor da compra for de R$ 739,50 reais? Já temos um padrão. Está na hora de colocarmos a nossa solução para atender todos os N casos possíveis. Vamos primeiro criar o teste.
Rode os testes. Esse último não passará. Vamos alterar a classe de negócio, mas dessa vez buscando atender todos os casos.
Rode os testes e veja que teremos sucesso em todos os 4.
Refactoring
Eita! Esse código está feio. Agora que criamos a nossa tela de proteção e nosso código está passando em todos os testes, podemos refatorá-lo. Vamos utilizar algumas práticas de Clean Code (Código Limpo) e tirar os números mágicos e espaços desnecessários.
Sempre execute os testes após o refactoring para ver se tudo ainda está funcionando.
Resumo do artigo
Neste artigo, falamos sobre o Test Driven Development (TDD). Ensinamos o ciclo Red, Green, Refactor que você deve utilizar durante todo o desenvolvimento. A estrutura de um bom código de teste com Arrange, Act e Assert. O porquê você deve caminhar com passos de bebês (baby steps), a estratégia de criação de código de negócio 1, 2, N. Também desmistificamos a ideia de que refatorar é algo ruim. Na verdade, é algo desejável quando estamos melhorando a qualidade do nosso código-fonte.
Se você chegou até aqui, já sabe o básico sobre TDD. Pode parar de ler e começar a praticar. Abaixo vou colocar apenas duas situações que costumam a acontecer e seria interessante que você soubesse tratá-las.
Quer saber mais sobre esse assunto?
O código deste artigo está disponível no GitHub no link https://github.com/avelinoferreiragf/K21-CSD
Aprenda a fazer TDD na prática e pratique outras técnicas ágeis de desenvolvimento no nosso treinamento de Testes Automatizados com Práticas Ágeis. Se quiser ir além e ainda aprender a como fazer a Entrega Contínua (Continous Delivery) faça o nosso treinamento de Certified Scrum Developer (CSD).
Veja outros posts relacionados:
- Como garantir a qualidade do software: Testes automatizados
- Testes automatizados? Por onde eu começo?
- Testes Automatizados – O que a falta deles pode causar?
Também leia os livros citados neste artigo:
- Beck, Kent.Test Driven Development: By Example (ISBN 13: 978-0321146533). Ed.Addison-Wesley Professional. 2002.
- Martin, Robert. Código Limpo. Habilidades Práticas Do Agile Software (ISBN-13: 978-8576082675). Ed. Alta Books. 2012.
Epílogo Parte I: O patrão ficou maluco!
Deu a louca no chefe. A promoção deu tão certo que agora ele quer dar um desconto de 20% nas compras maiores do que R$ 1.000,00.
O Product Owner criou a história de usuário: Eu, enquanto comprador, desejo receber um desconto de 20% nas compras acima de R$1.000,00, pois tenho que comprar muitos presentes e quero continuar economizando meu 13º.
Critério de Aceitação 03.
Dado que o cliente fez uma compra de R$ 1.000,00
Quando fechar a compra
Então recebe um desconto de 20% E o valor final da compra passa a ser R$ 800,00.
O que fazemos primeiro? …. Isso mesmo, o teste que valida essa condição.
Rode os testes e vamos receber um erro informando que o critério de aceitação não foi atendido.
Vamos implementar apenas o mínimo para que continue rodando.
Crie quantos testes você achar relevantes. Principalmente testando valores limites como R$ 999,99. Refatore quando já tiver criado a sua tela de proteção e tudo estiver verde.
Epílogo Parte II: O programador descuidado.
O time do marketing quer estimular a compra por impulso e quer fazer um experimento dando um desconto de 5% para compras menores do que R$ 500,00. O PO foi lá e criou uma nova história de usuário: Eu, enquanto marketing, desejo estimular a compra por impulso oferecendo um desconto de 5% tal que aumente meu faturamento em 20%. O PO em conjunto com o time fez o seguinte critério de aceitação:
Critério de Aceitação 04
Dado que o cliente fez uma compra de R$ 499,99
Quando fechar a compra
Então recebe um desconto de 5% E o valor final da compra passa a ser R$ 474,99.
O problema foi que entrou um novo programador entrou no time e ele não fez o treinamento de Testes Automatizados com Práticas Ágeis e nem o de Certified Scrum Developer. Também não leu este belo artigo. A primeira coisa que ele fez foi mexer no código de negócio.
O que acontecerá? Se você utiliza um servidor de integração contínua como o Jenkins, ele executará automaticamente todos os testes sempre que detecta alterações no seu repositório de controle de versão (Subversion ou Git, por exemplo). E dará um erro, pois uma regra de negócio foi alterada e os testes não.