Testes e qualidade de software
Estratégia de automação de testes: o que automatizar, a que custo, sem maquiar números
Avançado12 min de leituraAtualizado
Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês
Todo teste automatizado é um ativo com um passivo de manutenção embutido. Este guia trata do portfólio: escolher quais verificações merecem automação, moldar a suíte para que o feedback chegue na ordem certa, gerenciar ambientes e dados, aplicar uma política de testes instáveis que as pessoas realmente sigam e escolher métricas que revelem alguma coisa.
O que você vai aprender
- Avaliar um teste candidato em relação ao custo de manutenção esperado antes de automatizá-lo
- Projetar uma suíte em camadas com orçamentos explícitos de tempo de feedback para cada nível
- Operar uma política de testes instáveis que preserve a confiança no build
- Usar cobertura e quality gates como sinais, e não como metas a cumprir
- Escolher métricas de automação que mudem decisões em vez de enfeitar dashboards
- Posicionar os testes exploratórios ao lado de uma suíte automatizada, e não contra ela
Um teste automatizado não sai de graça depois de escrito. Ele ocupa minutos de build a cada execução, precisa ser atualizado sempre que o comportamento que cobre muda legitimamente, vai falhar de vez em quando por motivos que não têm nada a ver com o código, e alguém vai ter de lê-lo anos depois para entender o que ele estava protegendo. Uma estratégia de testes é, portanto, uma decisão de portfólio: quais desses passivos vale a pena carregar, e em que proporções.
Os times raramente fracassam por terem automatizado pouco da coisa certa. Fracassam por terem automatizado muito da coisa errada — centenas de testes de navegador que levam uma hora e falham às terças-feiras, testes unitários verificando que um mock foi chamado, números de cobertura obtidos exercitando getters. Este guia trata das escolhas que evitam isso, e parte do princípio de que você já sabe escrever os testes em si.
Automação é investimento
Antes de automatizar uma verificação, estime três coisas. Com que frequência ela vai rodar? Uma verificação que roda a cada commit durante três anos é executada milhares de vezes; a de uma migração pontual roda uma vez. Quão estável é o que ela observa? Um teste atrelado a uma regra de negócio sobrevive a redesenhos; um teste atrelado a um seletor CSS ou à ordem dos campos de um JSON, não. Quanto vai custar mantê-la funcionando? Esse é o número que as pessoas esquecem, e é ele que domina o total.
Multiplique esses fatores e algumas decisões conhecidas ficam óbvias. Automatizar um teste de valor-limite sobre uma regra de preço é barato e duradouro. Automatizar uma jornada completa no navegador até o checkout é caro, frágil e ainda assim vale a pena — uma vez, para o caminho mais valioso. Automatizar essa mesma jornada para onze variantes de cliente é onde o retorno fica negativo.
Algumas coisas valem a automação por motivos que não são a detecção de defeitos: a preparação de um ambiente via script, um gerador de dados, um script que reproduz um chamado de suporte. Automatizar tarefas costuma render mais do que automatizar verificações, e isso nunca aparece em um relatório de cobertura.
O que deve continuar manual: qualquer coisa que exija julgamento (esta página parece correta? esta mensagem de erro ajuda?), qualquer coisa que rode uma única vez, qualquer coisa cujo resultado esperado não possa ser enunciado sem ambiguidade, e o trabalho exploratório descrito no fim deste guia.
Formatos: pirâmide, troféu, favo de mel
A pirâmide clássica coloca muitos testes rápidos e isolados na base, menos testes de nível de serviço acima e um punhado de testes end-to-end no topo. Ela expressa um trade-off real — amplitude de cobertura contra velocidade, estabilidade e precisão de diagnóstico — e, para um sistema com lógica de domínio substancial, continua sendo um bom conselho.
O testing trophy alarga o meio: uma base de análise estática e checagem de tipos, um número modesto de testes unitários, um grande volume de testes de integração e poucos end-to-end. Ele serve para sistemas que são basicamente composição — um serviço que valida uma requisição, chama duas dependências e grava uma linha quase não tem lógica para teste unitário, e suas falhas interessantes estão todas nas fronteiras. O favo de mel (honeycomb) faz o mesmo argumento para microsserviços, com predominância de testes integrados e uso intenso de testes de contrato para evitar suítes end-to-end entre serviços.
Discutir qual diagrama está certo é desperdiçar uma reunião. O invariante por trás de todos eles: prefira o teste mais estreito capaz de detectar a falha que importa, porque testes estreitos são mais rápidos, mais estáveis e apontam a própria causa. O que determina o formato é onde o risco do seu sistema realmente está. Um motor de cálculo de impostos tem formato de pirâmide. Um API gateway, não.
Uma regra sobrevive a todas as variantes: testes end-to-end são um bisturi, não um cobertor. Mantenha poucos, cobrindo as jornadas de negócio críticas, aceite que são lentos e nunca os use para verificar uma regra que um teste mais barato poderia verificar.
Níveis e orçamentos de feedback
Decida quanto cada nível pode custar — e faça cumprir.
| Nível | Quando roda | Orçamento | Conteúdo |
|---|---|---|---|
| Compilação e análise estática | A cada salvamento e a cada push | segundos | Tipos, linters, detectores de bugs |
| Unitário | A cada push | menos de 2 minutos | Lógica, limites, ramos de erro |
| Integração | A cada pull request | menos de 10 minutos | Persistência, serialização, clientes, contratos |
| End-to-end | No merge para main | menos de 20 minutos | Um punhado de jornadas críticas |
| Não funcional | Toda noite ou a cada release | o tempo que for preciso | Carga, resistência, varreduras de segurança |
Os orçamentos são a estratégia. Quando um nível estoura o orçamento, alguma coisa tem de ceder — paralelizar, dividir ou descer testes de nível — e ter combinado o número de antemão transforma isso em uma decisão de rotina, e não em um debate. Um pipeline de pull request que vai aos poucos passando dos quinze minutos muda o comportamento dos desenvolvedores: eles juntam mudanças, trocam de contexto e começam a fazer merge na base da esperança.
Convenções de nomenclatura fazem a separação mecânica. O Surefire roda as classes *Test, o Failsafe roda as classes *IT em uma fase posterior, e os valores de @Tag do JUnit permitem incluir ou excluir fatias em uma mesma execução. A execução paralela costuma ser o grande ganho mais barato disponível; ela também força a independência entre testes, que vale a pena ter de qualquer forma.
Ambientes e dados de teste
Dois recursos compartilhados causam a maior parte dos problemas operacionais de uma suíte de testes, e ambos têm a mesma solução: parar de compartilhá-los.
Ambientes. Um único ambiente de homologação no qual todo time precisa fazer deploy antes de rodar os testes vira uma fila com lista de inscrição. Substitua-o sempre que possível por dependências efêmeras iniciadas pelo próprio teste, como descrito no guia de testes de integração. Mantenha um ambiente compartilhado para o que realmente não pode ser conteinerizado — um sandbox de terceiros, um mainframe — e trate o tempo nele como o recurso escasso que é.
Dados de teste. Um conjunto de dados compartilhado do qual tudo depende vira intocável: ninguém consegue alterar uma linha porque não sabe qual dos duzentos testes quebraria. A alternativa são dados criados pelo teste que precisa deles, por meio de builders, e removidos depois ou tornados à prova de colisão com chaves únicas. Para dados que precisam ter o formato dos de produção, use um gerador alimentado por um extrato anonimizado em vez de uma cópia — dados de produção em um ambiente de teste são um incidente de compliance esperando uma auditoria.
Uma política de testes instáveis que se sustenta
Um teste que falha de forma intermitente em código inalterado não é um incômodo menor; é um ataque ao valor da suíte inteira. Quando “roda de novo” vira resposta normal ao vermelho, as falhas reais também são reexecutadas, e a suíte deixa de ser um sinal.
Escreva a política antes de precisar dela, porque na hora sempre há uma release para entregar:
- Detectar. Registre todos os resultados de teste ao longo do tempo, não só o mais recente. Um teste que falhou duas vezes nas últimas cinquenta execuções em código inalterado é instável por definição, e não há como saber disso sem histórico.
- Colocar em quarentena imediatamente. Mova-o para um job não bloqueante no mesmo dia. O build precisa voltar a ser confiável em horas, não em sprints.
- Abrir um ticket com evidências. Anexe as execuções com falha, os tempos, a carga da máquina. Instabilidade é um defeito com causa, geralmente estado compartilhado, uma suposição de timing, uma dependência de ordem ou uma race condition real no código de produção.
- Corrigir ou apagar dentro de um prazo fixo. Duas semanas é uma escolha comum. Um teste em quarentena que ninguém corrige não oferece proteção nenhuma e ainda consome minutos de build; apagá-lo pelo menos torna a lacuna visível.
- Acompanhar a contagem. O número de testes em quarentena é uma métrica de saúde. Se está subindo, a suíte está se deteriorando.
Uma ressalva que vale dizer com todas as letras: às vezes um teste instável indica uma race condition real no código de produção, que em algum momento vai se manifestar como uma falha intermitente em produção. Apagar o teste sem investigar é como um bug real acaba abafado.
Cobertura: leia-a, não a persiga
Cobertura de linhas e de ramos mede qual código foi executado enquanto os testes rodavam. Ela não mede se algo foi verificado, se as asserções faziam sentido nem se os casos importantes foram cobertos. Uma suíte pode executar cada linha de um sistema sem verificar absolutamente nada.
Usada como sinal, a cobertura é realmente útil. Ordene por linhas descobertas e você encontra o módulo em que ninguém ousava mexer, o ramo de erro que nunca rodou, a classe adicionada no trimestre passado sem nenhum teste. Veja o que está descoberto e decida se isso importa.
Usada como meta, ela corrompe. Exija 80% e você terá 80%, alcançados pelo que for mais barato — testes sobre código gerado, laços que executam tudo e não verificam nada, e exclusões de cobertura aplicadas às partes difíceis. O gate que funciona na prática é aplicado apenas ao código novo e alterado: uma base de código legada não precisa ser adaptada retroativamente, mas nada novo entra sem testes. É exatamente isso que um quality gate do SonarQube sobre código novo impõe, e o guia de análise estática mostra como configurá-lo.
Mutation testing responde à pergunta que a cobertura não consegue: ferramentas como o PIT modificam o bytecode — invertem uma condição, trocam um valor de retorno — e rodam os testes de novo. Uma mutação que sobrevive significa que nenhum teste percebeu a mudança, o que identifica asserções que faltam em vez de linhas não executadas. É lento demais para rodar em todo build e excelente como auditoria periódica de um módulo crítico.
Gates no pipeline
Um quality gate é um critério de saída automatizado. Cada um que você adiciona deixa o pipeline mais lento e mais propenso a bloquear uma mudança legítima, então escolha com critério. Um conjunto defensável para um serviço:
- Compilação com warnings tratados como erros no código novo.
- Testes unitários verdes — sem exceções, sem desvio pela quarentena.
- Cobertura das linhas alteradas acima de um piso combinado.
- Nenhum novo apontamento crítico de análise estática.
- Nenhuma nova vulnerabilidade de alta severidade em dependências.
- Testes de integração e verificação de contratos verdes antes do merge.
Duas regras tornam os gates suportáveis. Gates rápidos primeiro, para que as falhas comuns apareçam nos dois primeiros minutos, e não no vigésimo. E nada de exceções permanentes: um gate que pode ser contornado marcando uma caixa de seleção será contornado rotineiramente em um mês. Se um gate erra com frequência suficiente para as pessoas quererem pulá-lo, conserte o gate. Como tudo isso se encaixa no pipeline mais amplo é assunto do guia de entrega contínua.
Responsabilidade e hábitos
Os testes pertencem ao time dono do código. Um grupo de automação separado escrevendo testes contra o serviço de outro time cria uma passagem de bastão, uma fila e testes que falham sem que ninguém capaz de lê-los esteja de plantão. Testadores especialistas são valiosos — em design de testes, sessões exploratórias, trabalho não funcional e na construção do harness compartilhado que deixa todo mundo mais rápido —, mas não como uma fábrica terceirizada de asserções.
Três hábitos distinguem os times cujas suítes continuam saudáveis:
- Build quebrado é a prioridade máxima. Não “alguém vai dar uma olhada”. Ou corrigido ou revertido dentro de um prazo curto e combinado.
- Testes passam por revisão como código de produção. Duplicação, nomes pouco claros e excesso de mocks recebem comentários. Um teste que ninguém entende não pode ser mantido.
- Todo defeito de produção ganha um teste. Escrito primeiro, reproduzindo a falha, antes da correção. Essa prática, sozinha, vai direcionando a suíte para onde os defeitos realmente acontecem no seu sistema, e não para onde você imaginava que aconteceriam.
Métricas que mudam decisões
A maioria dos dashboards de teste mostra números sobre os quais ninguém age. Os que valem a pena acompanhar são aqueles em que uma variação dispara uma decisão:
- Tempo de feedback por nível, acompanhado como tendência. Sem atenção, ele só piora.
- Causas de falha de build, classificadas como defeito real, teste instável ou infraestrutura. Se as instáveis superam as reais, a suíte tem um problema de credibilidade.
- Defeitos escapados, ou seja, problemas em produção que um teste poderia plausivelmente ter pegado. Cada um levanta a pergunta de qual camada deixou passar.
- Taxa de falha de mudanças e tempo médio de recuperação — as métricas de entrega que os testes existem para melhorar. Se a suíte cresce e elas não melhoram, o esforço está indo para um lugar improdutivo.
- Quantidade de testes em quarentena, como descrito acima.
Números que não vale a pena reportar: total de testes (mais testes não é melhor), taxa de aprovação (deveria ser 100%, ou você tem uma falha de política) e linhas de código de teste.
O lugar dos testes exploratórios
Verificações automatizadas conferem o que você já tinha pensado. Elas não encontram o problema que ninguém previu, e essa categoria inclui a maioria dos interessantes: fluxos confusos, uma mensagem que não faz sentido no contexto, um comportamento tecnicamente correto e comercialmente errado.
Os testes exploratórios cobrem essa lacuna e são uma atividade que exige habilidade, não cliques aleatórios. Eles acontecem em sessões com tempo delimitado e um charter escrito (“explorar reembolsos de pedidos enviados parcialmente, procurando estados dos quais a interface não consegue se recuperar”), anotações feitas durante a sessão e achados reportados depois. Tudo o que eles encontram e que vale verificar para sempre vira um teste automatizado; o resto fica como conhecimento sobre o sistema.
Uma divisão de trabalho útil: máquinas reverificam o que é conhecido, pessoas investigam o que é desconhecido. Times que automatizam tudo e não exploram nada entregam produtos que passam em todos os testes e decepcionam todos os usuários, e o guia de fundamentos de testes explica por que isso é um problema de validação, e não de verificação.
Principais conclusões
- Todo teste automatizado carrega um passivo de manutenção; decida se a verificação vai rodar com frequência suficiente e ser estável o bastante para valer a pena.
- Prefira o teste mais estreito capaz de detectar a falha — é esse princípio, e não um diagrama de pirâmide específico, que todos os formatos tentam aproximar.
- Dê a cada nível um orçamento de feedback combinado e faça valer esse acordo; um pipeline de pull request lento muda o comportamento dos desenvolvedores, sempre para pior.
- Pare de compartilhar ambientes e dados de teste; dependências efêmeras e dados por teste eliminam de uma vez a maior parte dos problemas operacionais.
- Tenha uma política escrita para testes instáveis — detectar, colocar em quarentena, abrir ticket, corrigir ou apagar —, porque uma suíte que as pessoas reexecutam em vez de confiar nela não tem valor.
- Leia a cobertura, não a persiga; aplique gates ao código novo, audite módulos críticos com mutation testing e mantenha os testes exploratórios no conjunto para o que a automação não enxerga.
Fontes e leituras complementares
Perguntas frequentes
- Que porcentagem dos testes deve ser automatizada?
- A pergunta pressupõe um conjunto fixo de verificações a dividir, e não é assim que funciona. A automação vale a pena para verificações que serão repetidas muitas vezes, têm um critério de aprovação inequívoco e são baratas de manter funcionando. Investigação exploratória, julgamento de usabilidade e verificações pontuais merecem o tempo de uma pessoa e nunca valem a automação. Procure automatizar tudo o que roda a cada commit e pare de pedir uma porcentagem.
- 80% de cobertura de código é uma boa meta?
- É uma observação razoável e uma meta ruim. Assim que um número é imposto, o jeito mais barato de alcançá-lo é escrever testes que executam código sem verificar nada, o que aumenta a métrica e diminui o valor. Use a cobertura para encontrar código que nenhum teste toca, aplique limites às linhas recém-alteradas em vez de à base de código inteira e analise o que está descoberto em vez de correr atrás de um número.
- O pipeline deve reexecutar automaticamente os testes que falham?
- Reexecutar para deixar o build verde esconde a falha e garante que a instabilidade fique para sempre. Uma reexecução de escopo restrito pode ser aceitável como medida temporária se cada nova tentativa for registrada e reportada, para que o time veja quais testes são instáveis e com que frequência. Sem essa visibilidade, as reexecuções automáticas transformam um defeito conhecido em um defeito invisível.
- Quem deve ser dono dos testes automatizados, os desenvolvedores ou um time de QA?
- O time dono do código é dono dos testes que rodam no seu build, porque um teste ilegível justamente para quem poderia corrigi-lo vira um bloqueio. Testadores especialistas agregam mais valor no design de testes, em sessões exploratórias, em testes não funcionais e na construção de ferramentas compartilhadas — não como um grupo separado escrevendo verificações a posteriori sobre o código dos outros. Um time de automação separado cria uma fila e uma passagem de bastão, exatamente o que a entrega contínua tenta eliminar.