A atividade de teste de software é inegavelmente crucial para o processo de desenvolvimento. Embora a escrita de código de testes (test code) não garanta a ausência de bugs em produtos, a falta de testes tornam o processo de desenvolvimento mais frágil, uma vez que se torna mais difícil repetir o comportamento de um erro inesperado e garantir que o problema não volte a acontecer.
Dessa forma, pessoas desenvolvedoras de software contam com um rico arsenal de prática, técnicas e ferramentas para apoiar o processo de escrita de testes de software.
Aqui mesmo, no blog da Zup, já discutimos diversas abordagens de testes, das mais tradicionais como teste manual, até estratégias mais sofisticadas como testes fuzzing, passando por testes de contratos, pelo papel do testador, dentre vários outros.
Refletindo sobre a escrita de código de testes
No entanto, embora existam diversas técnicas e processos, é importante refletir e debater sobre quais seriam as melhores práticas para escrita de código de testes ou test code.
Por exemplo, até que ponto devemos nos preocupar com essa etapa que avalia o comportamento excepcional da nossa aplicação? Ou, por exemplo, qual seria o tamanho ideal (em termos de linha de código) para um código de testes? Ou ainda, se faz sentido testar os métodos mais simples da minha aplicação, como gets e sets, ou mesmo um equals ou hashcode?
Possivelmente, dúvidas como essas e similares, já devem ter passado pela cabeça de devs que curtem escrever código de testes. Nesse momento, o mais comum é confiar na opinião da pessoa desenvolvedora mais experiente do time ou talvez consultar algum texto de blog ou livro.
Entretanto, embora a opinião das pessoas mais experientes, que inclusive se tornam referências nas comunidades técnicas sejam relevantes, no final do dia, não deixa de ser a opinião de uma única pessoa, que tem suas próprias crenças e vieses.
Aumentando a percepção sobre a qualidade de um código de testes
Como forma de tentar minimizar esses vieses e obter uma percepção mais ampla sobre as boas práticas para escrita de código de testes, um grupo de pesquisa fez um estudo composto de duas etapas.
Primeira etapa
Em um primeiro momento, profissionais de pesquisa selecionaram 21 pessoas desenvolvedoras experientes, que trabalham em empresas de tecnologias renomadas, bem como em projetos de software livre da Apache. Essas pessoas foram entrevistadas com o objetivo de entender determinadas questões, como:
- Como você definiria um bom código de teste?
- Quais critérios você utilizaria para caracterizar a qualidade de um código de teste?
- Quais fatores você considera quando vai escrever um código de teste?
Nessa etapa, foram analisadas todas as entrevistas e levantadas 29 características que poderiam ajudar a constituir um bom código de teste. Essas características foram agrupadas em seis dimensões: conteúdo do código de teste, tamanho e complexidade, cobertura, manutenção, capacidade de detectar bugs e outras.
Segunda etapa
Após apurarem essa lista, o time de pesquisa foi além e fez um segundo estudo. Nessa etapa, 261 pessoas desenvolvedoras de empresas de tecnologia receberam o convite para responder um questionário de validação.
Nesse questionário, era preciso priorizar as 29 características descobertas anteriormente. O objetivo desta segunda etapa era entender quais poderiam ser as características mais importantes que as pessoas desenvolvedoras poderiam se apoiar para criar seus códigos de testes.
A tabela a seguir apresenta a lista de características dos códigos de teste. Na última coluna desta mesma tabela, está a média das avaliações das pessoas que responderam o questionário de validação.
A escala da validação ia de um (pouco relevante) até cinco (muito relevante). Logo, os itens com valor mais próximo de cinco indicam as características mais relevantes, de acordo com as pessoas que responderam a pesquisa.
Característica | Votos |
Conteúdo | |
Um bom caso de teste deve ser específico ou atômico, ou seja, deve testar um único aspecto de um requisito | 3.93 |
Casos de teste em uma suíte de testes devem ser autocontidos, ou seja, devem ser independentes uns dos outros | 3.95 |
Bons casos de teste devem verificar o fluxo normal e excepcional | 4.47 |
Os casos de teste devem executar a análise de valor de limite, ou seja, tomar como valores de entrada os extremos de um domínio de entrada | 4.24 |
Os casos de teste devem servir como uma boa documentação de referência | 3.93 |
Tamanho e complexidade | |
A maioria dos casos de teste deve ser pequena em tamanho (em termos de linhas de código) | 3.85 |
Casos de teste grandes geralmente são difíceis de entender e manter | 3.73 |
Casos de teste grandes podem ser necessários para detectar bugs difíceis | 3.59 |
Um bom conjunto contém muitos casos de teste pequenos (com menos LOC) e poucos casos de teste grandes | 3.97 |
O aumento da complexidade em um caso de teste pode levar a erros no próprio código de teste | 4.04 |
Cobertura | |
A cobertura de código é necessária, mas não suficiente | 3.97 |
A cobertura de código deve ser usada para entender o que está faltando nos testes e criá-los com base nisso | 3.97 |
Maior cobertura não significa que um conjunto de testes pode detectar mais bugs | 4.02 |
Cada caso de teste deve exercitar uma quantidade de código pequena | 3.92 |
Um caso de teste projetado para maximizar a cobertura geralmente é longo, incompreensível e frágil (ou seja, quebra facilmente) | 3.50 |
Projetar casos de teste para cobrir requisitos diferentes geralmente é mais importante do que projetar casos de teste para cobrir mais códigos | 4.00 |
Manutenibilidade | |
Um bom caso de teste deve ser bem modularizado | 4.62 |
Um bom caso de teste deve ser legível e compreensível | 4.58 |
Os casos de teste devem ser mais simples do que o código que está sendo testado | 4.20 |
O código de teste deve ser projetado tendo em mente a capacidade de manutenção, pois a evolução do código geralmente requer alterações no código de teste | 4.16 |
Os links de rastreabilidade devem ser mantidos entre o código de teste, os requisitos e o código-fonte | 3.97 |
Detecção de bugs | |
Um bom caso de teste deve tentar interromper a funcionalidade para encontrar possíveis bugs | 4.11 |
Teste até as coisas mais simples que não podem dar errado | 3.89 |
Durante a manutenção, quando um bug é corrigido, é bom adicionar um caso de teste que o cubra | 4.40 |
As asserções de teste podem ajudar a detectar erros sutis que, de outra forma, poderiam passar despercebidos | 4.51 |
Adicionar erros comuns e possíveis causas como comentários no código de teste é útil para depurar falhas | 3.98 |
Outros | |
Um bom caso de teste deve ser projetado de forma que seus resultados sejam determinísticos | 4.07 |
Os casos de teste em um conjunto de testes não devem ter efeitos colaterais, portanto executar um teste antes ou depois de outro não deve alterar os resultados | 4.28 |
Os casos de teste devem usar tags ou categorias, como testes lentos, testes rápidos, etc., para poder executar um conjunto específico de testes facilmente por vez | 3.93 |
As principais características da escrita de código de testes
De acordo com a pesquisa, as três características mais relevantes dentre as mais votadas são as seguintes:
Um bom caso de teste deve ser legível e fácil de se compreender (4.58)
Mais de 96% de profissionais acreditam que códigos de testes devem ser fáceis de ler e entender. Essa foi a característica mais votada do estudo, o que é interessante, pois não está associada diretamente com a capacidade do teste de revelar bugs e, sim, com a capacidade do código ser mantido e evoluído.
Uma das pessoas que participou do estudo complementou dizendo que “Como qualquer código, se você precisar mantê-lo, é melhor entendê-lo.” No entanto, outra pessoa comentou que embora essa seja uma característica importante, não é necessariamente fácil de se alcançar: “É um desafio manter o teste de unidade com boa aparência.”
Em um outro estudo, foi observado que códigos de testes que são difíceis de serem entendidos são também mais difíceis de serem corrigidos em caso de falha.
Assertions ajudam a detectar erros sutis que, de outra forma, poderiam passar despercebidos (4.51)
Assertions são construções de código de teste que ajudam, através de comparações de valores, avaliar se uma determinada propriedade tem o comportamento que se espera observar.
A maioria das pessoas acredita que o uso de assertions é essencial na escrita de bons casos de teste. Como uma pessoa exemplificou: “você precisa ter asserções em um teste, caso contrário, você está apenas exercitando o sistema”.
Essa observação está também alinhada com outros estudos que indicam que o uso de assertions está fortemente correlacionado com a efetividade da suíte de testes.
Bons casos de teste devem verificar o fluxo normal e excepcional (4.47)
A terceira característica mais votada foi a necessidade de escrever testes que exercitem o comportamento normal e também o excepcional das nossas aplicações.
Inclusive, uma pessoa que respondeu a pesquisa comentou que “Você se concentra no caso feliz para verificar se a funcionalidade de negócios está OK… e então (escreve o código de teste) para garantir que todos os casos extremos foram tratados adequadamente.”
Outras características interessantes
As três primeiras características acima são mais gerais e, talvez por isso, foram mais bem votadas. Porém, também houve algumas características mais técnicas que tiveram um bom nível de votação, como:
- A cobertura de código é necessária, mas não suficiente (no sentido de garantir que um código está livre de bugs);
- Os casos de teste devem executar a análise de valor de limite;
- Os casos de teste de uma suíte de testes devem ser independentes uns dos outros.
Por conta do nosso espaço aqui, vamos deixar a discussão sobre as demais características para você discutir nos comentários. No entanto, para uma análise mais profunda das práticas, sugerimos a leitura completa do artigo ?.
Na Zup, inclusive, temos o clube do artigo, que é um momento em que zuppers se reúnem para ler e debater artigos científicos. Este artigo sobre qualidade dos testes foi tópico do nosso segundo encontro, que rendeu bastante discussão!
Falando em boas práticas, confira um episódio do Zup Tech Hour sobre programação com um time de especialistas:
Conclusão
A escrita de código de testes ou test code não é uma tarefa fácil, porém de suma importância. Neste texto, visitamos um artigo científico que derivou uma lista de características para bons códigos de testes.
Depois de entender um pouco mais sobre como avaliar a qualidade dos testes, será que podemos olhar para os testes que escrevemos e apontar onde podemos melhorar? Será que conseguimos empregar alguma dessas características no próximo teste que escreveremos?
Conte pra gente se você pensa em adotar algumas dessas características! Nossos comentários estão disponíveis para a discussão.