Teste de unidade: Como identificar a eficácia de um teste?
Um teste de unidade é um tipo de teste automatizado que verifica uma parte específica de um código de maneira rápida e isolada. O objetivo de um teste de unidade é garantir que o código esteja funcionando corretamente e que esteja fazendo o que é esperado dele. Ou seja, ele:
- Verifica uma parte pequena do código;
- De maneira rápida;
- E de uma maneira isolada.
Os desenvolvedores realizam os testes de unidade durante o processo de desenvolvimento do código, com o objetivo de identificar possíveis problemas ou erros de maneira rápida e precisa.
Eles também são úteis para garantir que o código continue funcionando corretamente mesmo após alterações ou refatorações.
Os testes de unidade são, geralmente, realizados em conjunto com outros tipos de testes, como testes de integração e testes de sistema, para garantir que a aplicação esteja funcionando de maneira adequada.
Neste conteúdo, vamos ajudar você a entender quais parâmetros utilizar para identificar quando um teste de unidade é bom ou não.
Princípios de um teste de unidade eficaz
Mais do que verificar se o código "roda" ou não, é importante garantir que o comportamento esperado da aplicação vai ser realizado sem erros, atingindo o objetivo final do negócio.
Dessa forma, é válido reiterar que testes automatizados não avaliam somente o código, mas o comportamento esperado daquele domínio de negócio.
Hoje em dia, a importância dos testes automatizados é amplamente reconhecida nos projetos de software com objetivo de garantir escalabilidade com qualidade.
Vladimir Khorikov, em seu livro Unit Testing: Principles, Practices and Patterns, propõe que é necessário pensar além de simplesmente fazer testes, mas também dar uma atenção especial para a qualidade, de maneira que os custos envolvidos na sua concepção e manutenção sejam os menores possíveis.
Segundo o autor, essa necessidade surgiu pois a busca por uma meta de cobertura de testes de unidade nos projetos acaba gerando uma quantidade grande de testes, que em alguns casos não identificam possíveis bugs no sistema e comprometem uma parte significativa do tempo de desenvolvimento.
Dessa forma, a seguir, apresentaremos os principais indicadores ao avaliar a eficácia de um teste de unidade ou não.
4 principais indicadores de um teste de unidade eficaz
Já trabalhou em um projeto onde, a cada mudança realizada, vários testes falham e você não identifica o porquê? Já teve que lidar com testes de difícil compreensão e foi necessário um tempo considerável para analisá-los?
Esses são alguns indicativos que apontam ser necessário repensar os testes de um projeto.
A seguir, vamos apresentar os quatro principais pontos, de acordo com Vladimir Khorikov, que nos ajudam a reconhecer bons testes de unidade.
Proteção contra regressão
A regressão é um bug no software e os testes devem ter capacidade de identificá-los. Quanto maior a quantidade de código de uma aplicação, mais exposta ela está a potenciais problemas.
Para garantir essa proteção, é necessário que os testes executem o máximo de código possível, aumentando a chance de revelar uma regressão.
Além disso, é necessário priorizar códigos de domínio do negócio e aqueles mais complexos, evitando avaliar comportamentos triviais da aplicação, por exemplo, métodos que só passam valores para propriedades de objetos.
A imagem mostra um exemplo de um teste de comportamento trivial. Ele verifica um método que apenas atribui um valor string ao parâmetro Name de um objeto User. O próprio framework realiza essa atribuição.
É necessário evitar esse tipo de teste e focar naqueles mais complexos ou que sejam realmente importantes para o negócio.
Fig 1. Teste de Unidade de código considerado "trivial"
Resistência a refatoração
Refatorar significa mudar um código existente sem alterar o comportamento da aplicação. Muitas vezes, nos deparamos com projetos que tem uma alta cobertura de testes, que atendem nossos objetivos em um primeiro momento, mas a cada refatoração, a cada mínima melhoria, os testes falham.
Nesses casos, é provável que em certo momento, essas falhas vão transformar o teste em um peso, o que está bem longe do seu objetivo.
Para garantir essa resistência, é necessário evitar que o teste esteja acoplado ao código implementado. Ele deve estar focado em "o que" a aplicação deve fazer e não "como".
A seguir vamos apresentar um teste que verifica o envio de uma expressão SQL correta para retornar um usuário com determinado ID. O teste é capaz de identificar bugs, mas existem outras expressões SQL que podem trazer o mesmo resultado.
Uma mudança no código já acarreta em falhas no teste, mesmo que a aplicação retorne o mesmo usuário, ou seja, tenha o mesmo comportamento. É necessário evitar esse tipo de teste.
Fig 2. Teste acoplado ao código implementado (Fonte: Vladimir Khorikov, Unit Testing: Principles, Practices and Patterns, 2020)
Feedback rápido
O feedback rápido é uma das propriedades básicas de qualquer teste de unidade. Quanto maior a velocidade de resposta, mais tempo você tem para lidar com atividades que importam.
Testes longos também tornam a pipeline de Integração Contínua mais onerosa, já que, na maioria dos casos, há uma etapa para executar os testes do projeto. A consequência disso é atraso no deploy da aplicação e aumento nos custos.
Não há um valor exato de tempo considerado bom ou ruim. Se o tempo de execução dos testes está tornando oneroso seu desenvolvimento, é um indício de testes mal construídos.
Ser de fácil manutenção
A manutenção é um elemento que deve sempre ser considerado, se queremos garantir a escalabilidade de nossas aplicações. Testes fáceis de se manter apresentam duas características:
- Ser fácil de ler: garantir a legibilidade do código para os desenvolvedores e especialistas no negócio é importante para um entendimento mais rápido do objetivo do teste e redução no custo de manutenção. Testes com menos linhas tendem a ser mais fáceis de serem compreendidos;
- Ser fácil de executar: é necessário criar uma infraestrutura para os testes, de forma que suas dependências (por exemplo, base de dados e APIs externas) se mantenham operacionais.
Como vimos, é importante não só saber escrever testes como também fatorá-los de maneira a melhorar a qualidade e dar mais segurança às nossas aplicações.
De qualquer maneira, é necessário avaliá-los a partir dessas quatro características. Isso garante o desenvolvimento de testes que sejam de baixo custo, fácil de se manter e que cumpram seu papel dentro da aplicação.
Este artigo foi escrito baseado no capítulo 4 do livro “Unit Testing: Principles, Practices and Patterns”. Eu sugiro a leitura de todo o material para aqueles que querem se aprofundar sobre esse tema.
*Agradecimento especial ao Valter Rodrigues, do Squad Hades, pelos apontamentos e revisão do texto*.