Tiago Aguiar recebe Yuri Marcel para uma conversa direta sobre testes de software: quando cada um ouviu falar do assunto pela primeira vez, quanto tempo levou até de fato implementar testes no dia a dia, e as dores de transformar um sistema monolítico e acoplado em algo minimamente confiável. A conversa passa por biblioteca própria em cima do Selenium, refatoração para permitir testes unitários, e termina em um problema mais sutil: testes que existem só para enganar a esteira de qualidade.
Yuri Marcel é desenvolvedor de software com carreira iniciada em 2011, mesma época de Tiago Aguiar. Passou por uma experiência de liderança técnica em um órgão governamental, onde herdou um sistema monolítico sem documentação e teve que introduzir testes para estabilizar o produto perante a área de negócio. Ao longo da carreira, trabalhou tanto com testes de integração em sistemas legados acoplados quanto com testes unitários em arquiteturas de microserviços, acumulando experiência prática nos dois extremos do espectro de maturidade em testes.
Yuri descreve um sistema tão acoplado e sem documentação que a equipe media a saúde de uma funcionalidade pela ausência de reclamações dos usuários — quanto menos gente reclamando, mais provável que aquele trecho estivesse simplesmente parado, não funcionando. Quando os testes de integração via Selenium começaram a rodar e a detectar quebras reais, essa lógica invertida deixou de fazer sentido, e a equipe teve que se readaptar à ideia de que silêncio dos usuários não era mais sinônimo de sistema saudável.
"A gente calculava que uma da terminada funcionalidade do sistema estava funcionando quando tinha muitos usuários reclamando. [...] Se não tem um usuário reclamando é porque essa funcionalidade não está funcionando." por Yuri Marcel
Diante de um time que não tinha facilidade com o Selenium, Yuri optou por construir uma camada própria de abstração: uma biblioteca com classes e métodos que permitia ao restante do time escrever testes de integração apenas informando o ID de um botão e o comportamento esperado, sem lidar diretamente com a API do Selenium. Essa biblioteca era acoplada àquela plataforma específica, mas resolveu o problema prático de reduzir a barreira de entrada para o time escrever testes.
"Eu meio que criei ali uma biblioteca para utilizar o selênio naquela plataforma. [...] O time só chamava a biblioteca, colocava o ID do botão, e o que ele queria que o botão fizesse e os campos que ele queria que fosse validados." por Yuri Marcel
Em um monolito extremamente acoplado, testes unitários simplesmente não eram viáveis — qualquer teste de uma "unidade" acabava testando um módulo inteiro por causa das dependências diretas com banco de dados e outras classes. A única solução que Yuri encontrou, em projetos posteriores já com microserviços, foi refatorar o código para introduzir injeção de dependência, permitindo mockar recursos externos e isolar de fato a responsabilidade de cada classe.
"Testes unitários não vai conseguir contemplar quando a classe é dessa forma acoplada com muita responsabilidade. [...] A única solução: fatorar o código." por Yuri Marcel
Um dos pontos mais reveladores da conversa é o relato de testes unitários escritos deliberadamente para não testar nada — apenas para garantir a cobertura mínima de código exigida por ferramentas como o Sonar antes de liberar o deploy. Yuri conta o episódio de um cliente que insistia em unitários sobre controllers sem regra de negócio nenhuma, mockando módulos inteiros só para "passar" na esteira, mesmo sabendo que o teste não validava comportamento real algum.
"Ah, mas será que vai voltar mesmo? [...] Ele não tem a inteligência ainda para saber que aquele teste está testando nada, e quem tem inteligência está usando para coisa errada." por Yuri Marcel
Como resposta ao problema de testes vazios que só existem para cobrir métricas, Yuri apresenta o conceito de teste de mutação: uma ferramenta que altera automaticamente pequenos trechos do código (como inverter uma condição) e verifica se os testes existentes quebram diante dessa mudança. Se o teste não quebra quando o comportamento do código muda, é sinal de que ele não está testando nada de fato — uma camada de verificação sobre a própria suíte de testes.
"O teste de mutação vai lá e muda o seu código. [...] Se o seu teste realmente estiver funcionando, o teste de mutação vai fazer o seu teste quebrar. É para isso que ele serve." por Yuri Marcel
"Ninguém sabe qual era o deadline que ele tinha, como é que tava, que ele era um bug direto em produção, então a gente tinha que resolver na hora. Então eu sou bem compreensivo em cima desses pontos." por Yuri Marcel