Escrever testes não é uma tarefa fácil. Dificilmente aprendemos na faculdade e também não recebemos treinamento ou mentoria no trabalho.
Mas independente da linguagem, ferramentas e bibliotecas, algumas práticas que são a base para uma boa escrita de testes.
Neste artigo compartilho algumas destas dicas, em ordem de execução, para iniciar a jornada de uma boa escrita de testes!
Dados, Ação e Verificação#
Todos os testes devem ter três partes claras para uma boa execução dos testes. Vamos ver um exemplo.
class TestClass {
@Test
void ehAnoBissextoTest() {
// Dados
int ano = 2016;
// Ação
boolean resultado = AnoBissexto.ehAnoBissexto(ano);
// Verificação
Assertions.assertTrue(resultado);
}
}
Qualquer tipo de teste sempre terá estas três partes. Se faltar uma delas, provavelmente existe algo errado.
Os dados são necessários para a execução do teste.
Ação é a parte do seu software que você quer exercitar para verificar o resultado.
E a verificação existe para garantir que o comportamento esperado por você é o comportamento atual do seu software.
Se você sabe exatamente onde estão estas três partes, pode escrever de uma forma mais sucinta:
class TestClass {
@Test
void ehAnoBissextoTest() {
// Dados + Ação + Verificação
Assertions.assertTrue(AnoBissexto.ehAnoBissexto(2016));
}
}
Então, toda vez que você começar a escrever um teste, comece simples sempre pensando nestas três partes.
Dê atenção a nomes de classe e dos métodos de teste#
O segundo passo é começar a pensar em casos de testes.
Quando a função devolve true ou false?
Para deixar claro qual o caso de teste que estamos testando, aproveitamos o nome da classe e do método para especificar um cenário específico de teste.
O teste sempre deve verificar um comportamento específico do seu software.
Então dê nomes significativos e ubíquo para o nome das classes e métodos para um comportamento específico.
Um exemplo poderia ser:
class AnoBissextoSpec {
@Nested
class EhBissextoQuando {
@Test
void seEhDivisivelPor4MasNaoPor100() {}
@Test
void seEhDivisivelPor400() {}
}
@Nested
class NaoEhBissextoQuando {
@Test
void seNaoEhDivisivelPor4() {}
@Test
void seEhDivisivelPor100MasNaoPor400() {}
}
}
No exemplo acima, deixamos claro qual a responsabilidade de cada teste e qual o escopo que ele pertence.
Separamos por classe as situações positivas e negativas da função e os métodos deixam claro qual a especificação exata que estamos testando.
Aumente a amostra dos seus testes#
Um bom design de teste permite adicionar mais dados para serem executados.
Talvez o teste passe para um valor mas talvez não passe para um específico valor.
Ou seja, quanto for maior a quantidade de dados, ou amostras para seu teste, mais confiável o comportamento atual do seu software.
Um exemplo poderia ser assim:
class AnoBissextoSpec {
@Nested
class EhBissextoQuando {
@ParameterizedTest
@ValueSource(ints = {320, 240, 1280, 2160})
void seEhDivisivelPor4MasNaoPor100(int ano) {
System.out.println(ano);
}
@ParameterizedTest
@ValueSource(ints = {400, 800, 1600, 2400})
void seEhDivisivelPor400(int ano) {
System.out.println(ano);
}
}
@Nested
class NaoEhBissextoQuando {
@ParameterizedTest
@ValueSource(ints = {2018, 2017, 47, 1})
void seNaoEhDivisivelPor4(int ano) {
System.out.println(ano);
}
@ParameterizedTest
@ValueSource(ints = {1700, 1800, 300, 100})
void seEhDivisivelPor100MasNaoPor400(int ano) {
System.out.println(ano);
}
}
}
A ideia deste passo é aumentar a quantidade de dados que utilizamos para exercer o teste.
Serve também como exemplo de quais anos se encaixam em cada especificação.
Alguns frameworks e biblioteca de testes chamam esta técnica de Data-Driven-Testing.
Pense em casos negativos e de exceção#
Quais os cenários que o seu código não suporta? E o que acontece quando ocorre este cenário?
Então crie testes e cenários para estes casos também.
Um exemplo:
class AnoBissextoSpec {
@Nested
class EhBissextoQuando {
@ParameterizedTest
@ValueSource(ints = {320, 240, 1280, 2160})
void seEhDivisivelPor4MasNaoPor100(int ano) {
System.out.println(ano);
}
@ParameterizedTest
@ValueSource(ints = {400, 800, 1600, 2400})
void seEhDivisivelPor400(int ano) {
System.out.println(ano);
}
}
@Nested
class NaoEhBissextoQuando {
@ParameterizedTest
@ValueSource(ints = {2018, 2017, 47, 1})
void seNaoEhDivisivelPor4(int ano) {
System.out.println(ano);
}
@ParameterizedTest
@ValueSource(ints = {1700, 1800, 300, 100})
void seEhDivisivelPor100MasNaoPor400(int ano) {
System.out.println(ano);
}
}
@Nested
class NaoSuportaELancaExcecao {
@Test
void quando0AnoEhZero() {}
@ParameterizedTest
@ValueSource(ints = {-1, -100, -400})
void quando0AnoEhNegativo(int ano) {
System.out.println(ano);
}
}
}
É assim que encontramos bugs mais cedo.
Desta forma conseguimos trabalhar de forma preventiva.
Sempre verifique que o seu teste falha#
Um dos passos mais importantes e mais ignorado na escrita de um teste é a verificação de que ele falha quando você muda o código de produção.
Isso acontece principalmente quando o código de teste é escrito depois de um código de produção.
Não adianta seguir todas as dicas acima se o seu teste nunca falha. É um falso positivo.
Então, para cada teste novo, sempre verifique que ele falha.
Apague uma soma, mude o sinal de “>” para “<”, comente uma linha.
É a dica mais importante deste artigo!
Conclusão#
Para escrever um bom teste não adianta fazê-lo passar.
Um bom teste deve ter as seguintes características:
- As três partes claras: Dados, Ação e Verificação
- Uma boa amostragem para o teste
- Especificação clara que está sendo testada
- Casos negativos e de exceção
- O teste falha quando mudamos o código de produção
Com estas dicas, você poderá iniciar a jornada de uma boa escrita de testes.
Estas dicas podem ser utilizadas em qualquer linguagem, ferramenta.
E é possível aplicar em testes unitários, integração, contrato, API e etc.
Se você tiver dúvidas, sugestões ou até correções, sinta-se à vontade para comentar ou falar diretamente comigo!
🥒🥒 Até o próximo artigo! 🥒🥒


