segunda-feira, 18 de maio de 2020

Exemplo de teste automatizado com JUnit: FizzBuzz

Vamos apresentar aqui um exemplo de teste automatizado utilizando o JUnit, com base na abordagem TDD (Test Driven Development). De acordo com Santos Filho e Oliveira (2014), o TDD segue duas regras simples: a primeira é que um novo teste só deve ser escrito se um teste automatizado falhar, e a segunda é que deve-se buscar eliminar duplicidade. Apesar de simples, essas regras geram um complexo comportamento expresso pelo ciclo red-green-refactor (vermelho-verde-refatora), que é baseado em:
  • Vermelho: escrever um teste que não funciona;
  • Verde: fazer o teste passar, escrevendo uma pequena mudança no código, mesmo que não seja a mais adequada;
  • Refatora: melhorar o código, eliminando todas as duplicidades e mantendo-o verde.
“‘…TDD é simplesmente você escrever o teste do seu programa antes de codificá-lo de fato. Teste nesse contexto seriam os testes unitários.’

Ao meu ver a essência está correta, porém talvez faltasse deixar claro que TDD não se trata apenas de escrever testes antes do código. Esta é a primeira visão que a maioria dos iniciantes têm sobre desenvolvimento orientado a testes: que basta escrever todos os testes antes e pronto. Isto é compreensível, já que escrever testes antes do código já causa um choque por si só. Porém, TDD foi definido pelo Kent Beck com a seguinte fórmula:

TDD = Test-First + Design Incremental

E a parte do design incremental ficou um pouco confusa na continuação do texto:

‘…O processo de desenvolvimento usando TDD é muito simples:

1 - Escolha a classe e o método que você que codificar e pense em todos os testes possíveis para ela;

2- Antes de escrever a classe, codifique os testes que você pensou…’

Seguindo estas instruções (escrever todos os testes possíveis antes) pode-se perder um pouco de um dos maiores benefícios de TDD, que é prover feedback rápido sobre o que está sendo implementado. Aí que entra o design incremental. A ideia é que a solução seja criada em pequenos passos (Baby Steps), seguindo a rotina descrita no livro TDD by Example:

1 - Escreva um pequeno teste que falhe, e talvez até mesmo sequer compile inicialmente;

2 - Faça o teste passar da maneira rápida, cometendo qualquer ‘pecado’ durante este processo;

3 - Refatore: elimine toda duplicação criada para fazer os testes passarem.

Desta forma, não é preciso pensar na solução completa (sequer numa classe inteira) antes de começar, basta pensar em um teste apenas e fazê-lo passar. Com o conhecimento adquirido é possível então passar para o próximo teste com mais segurança, e assim avançar até que a solução esteja completa.

Outra noção comum é pensar que TDD trata apenas de testes de unidade.
DD faz bastante uso de testes unitários. Porém, embora a maioria dos exemplos mostra TDD sendo feito através deste tipo de teste, isto não significa que a técnica está restrita a eles. Escrever testes de aceitação simples antes do código existir também pode colaborar na hora de desenvolver com TDD, já que com eles também pode-se obter feedback sobre a solução.

Embora sejam bem pontuais, observar estes detalhes pode ajudar no uso mais eficiente de TDD.”

Para iniciar o planejamento dos testes, temos que partir de um problema específico e usaremos um caso clássico denominado problema FizzBuzz. O FizzBuzz consiste em exibir uma lista de 1 a 30, um em cada linha, e filtrar todos os números respeitando as regras:

Retorno do Fizz Buzz

números divisíveis por 3 devem retornar “Fizz”
números divisíveis por 5 devem retornar “Buzz”
números divisíveis por 3 e 5 devem retornar “FizzBuzz”
Fonte: Elaborado pela autora

Antes de começar a escrever os testes, é preciso definir o que precisa ser testado, para isso deve-se criar uma lista com todos os testes que sejam necessários. No nosso utilizaremos a seguinte lista para os testes:

[1] Retornar 1 ao passar 1;

[2] Retornar 2 ao passar 2;

[3] Retornar Fizz ao passar 3;

[4] Retornar 4 ao passar 4;

[5] Retornar Buzz ao passar 5;

[6] Retornar Fizz ao passar 6;

[7] Retornar 7 ao passar 7;

[8] Retornar 8 ao passar 8;

[9] Retornar Fizz ao passar 9;

[10] Retornar Buzz ao passar 10;

[11] Retornar FizzBuzz ao passar 15;

[12] Retornar FizzBuzz ao passar 30.

O teste [1] da lista deve ser implementado e retornar 1 quando passado 1 como parâmetro. O método que executa este teste é:

@Test
public void retornaUmParaUm(){
Fizzbuzz fizzbuzz = new Fizzbuzz();
assertEquals("1", fizzbuzz.verificaFizzbuzz(1));
}

Ao executar o teste é apresentado o seguinte resultado:

Nenhum teste passou, 1 teste causou 1 erro (0,99 s)
retornaUmParaUm causou um Erro: Uncompilable source code – Erroneous tree type: <any>

Na primeira tentativa, o teste não vai passar porque não existe a classe Fizzbuzz nem o método verificaFizzbuzz(). Assim, o próximo passo é fazer o teste passar com o mínimo de código. Para isto, deve ser implementada a classe Fizzbuzz e o método verificaFizzbuzz retornando 1, atendendo ao teste [1] da lista.

public class Fizzbuzz {
public String verificaFizzbuzz(Integer numero){
   return "1";
   }
}

Como resultado da execução do teste [1], obtém-se a seguinte saída:

O teste passou (0,98 s)

Agora é necessário implementar o teste [2] da lista, que retorna 2 quando passado 2 como parâmetro.

@Test
public void retornaDoisParaDois(){
Fizzbuzz fizzbuzz = new Fizzbuzz();
assertEquals("2", fizzbuzz.verificaFizzbuzz(2));
}

Ao executar esse teste, é apresentado um erro, pois o método verificaFizzbuzz vai retornar sempre 1. Executando o teste, é apresentado o seguinte resultado:

   1 teste passou, 1 teste falhou. (0,123 s)
   retornaDoisParaDois Falhou: expected <[2]> but was <[1]>

É necessário refatorar o código para fazê-lo passar. As mudanças apresentadas são:

public String verificaFizzbuzz(Integer numero){
return numero.toString();
}

Com essas mudanças, o teste [2] passou. Como resultado da execução foi apresentada a seguinte saída:

Ambos os testes passaram. (0,108 s)

Nenhum comentário:

Postar um comentário