Testes instáveis: O que os causa e como corrigi-los para sempre
Um teste que falha aleatoriamente ensina sua equipe a ignorar as falhas. Aprenda como confirmar se um teste está instável, rastrear a causa real e corrigi-lo, com demonstrações mostrando o que realmente funciona.
- Um teste instável passa e falha no mesmo código, sem alterações.
- As causas habituais são temporização, estado partilhado, dados instáveis, serviços externos e diferenças ambientais.
- Substitua as pausas fixas por esperas por uma condição específica e forneça a cada teste seus próprios dados.
- As novas tentativas podem ocultar bugs reais. Coloque um teste instável em quarentena, corrija-o dentro de um prazo ou exclua-o.
A compilação fica vermelha, alguém clica em “executar novamente”, ela é aprovada e todos seguem em frente. Parece inofensivo, mas testes instáveis destroem lentamente o que há de mais valioso em um conjunto de testes: a confiança das pessoas nele. Quando as falhas geralmente são barulhentas, bugs reais também são eliminados.
Este guia aborda por que os testes ficam instáveis e como corrigir cada causa adequadamente. Executamos as demonstrações de tempo e estado compartilhado no Python 3.13, e os números mostrados vêm dessas execuções. São pequenas demonstrações projetadas para facilitar a visualização da instabilidade, de modo que suas próprias taxas de falha serão diferentes.
O que é um teste instável?
Um teste instável fornece resultados diferentes no mesmo código. Ele passa em uma execução e falha na próxima, mesmo que nada tenha mudado no produto. O teste pode estar errado ou o código pode conter um bug intermitente real. Essa segunda possibilidade é exatamente a razão pela qual você não deve simplesmente descartá-los.
Por que os testes instáveis são tão caros
- Confiança perdida. Quando as pessoas presumem que vermelho significa “provavelmente esquisito”, elas param de investigar falhas reais.
- Perda de tempo. Os desenvolvedores passam horas reexecutando pipelines e examinando falhas que não têm nada a ver com suas alterações.
- Entrega mais lenta. Fusões e liberações aguardam repetições, minando o ponto de CI/CD.
- Bugs ocultos. As condições de corrida no produto podem parecer ruídos de teste, e são essas que prejudicam a produção.
Resumo das causas comuns
| Causa | Sinal típico | Correção usual |
|---|---|---|
| Tempo e assíncrono | Falha mais quando o CI está ocupado | Espere por uma condição |
| Estado compartilhado | Falha dependendo da ordem do teste | Isolar dados, redefinir estado |
| Datas e aleatoriedade | Falha em determinados horários ou dias | Controle o relógio e a semeadura |
| Seletores frágeis | Falha após alterações na IU | Use localizadores estáveis |
| Serviços externos | Falha quando uma dependência é lenta | Zombe ou esbofeteie-os |
| Diferenças ambientais | Passa localmente, falha no CI | Alinhar com contêineres |
| Limites de recursos | Falha em execuções paralelas | Ajuste os trabalhadores, corrija a contenção |
Causa 1: tempo e sono fixo
Esta é a causa mais comum. Um teste pressupõe que o trabalho em segundo plano será concluído após um atraso fixo. Para ver o efeito, escrevemos um pequeno teste no qual o trabalho leva até 2 ms, enquanto o teste espera apenas 1 ms antes de verificar:
def flaky_timing():
result = {}
def work():
time.sleep(random.uniform(0, 0.002))
result["done"] = True
threading.Thread(target=work).start()
time.sleep(0.001) # hope it has finished
assert result.get("done")
A solução é aguardar a condição real, com um limite superior generoso:
def fixed_timing():
result = threading.Event()
def work():
time.sleep(random.uniform(0, 0.002))
result.set()
threading.Thread(target=work).start()
assert result.wait(timeout=1) # continues as soon as it is ready
Executamos cada versão 200 vezes:
| Versão | Falhas em 200 |
|---|---|
| Sono fixo | 102 |
| Espere por uma condição | 0 |
O resultado é mais do que apenas confiabilidade. A versão fixa avança no momento em que o trabalho é concluído, por isso geralmente também é mais rápida. Um sono mais longo faria com que a primeira versão falhasse com menos frequência, mas cada execução seria mais lenta e ainda poderia falhar em uma máquina de CI ocupada.
Nos testes de navegador, o mesmo princípio se aplica. Use a espera automática de Playwright ou Esperas de Selenium explícitas, em vez de time.sleep:
# Playwright: retries until true or a timeout is reached
expect(page.get_by_text("Order placed")).to_be_visible()
# Selenium: an explicit wait for a condition
WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.ID, "confirmation"))
)
Causa 2: estado compartilhado e ordem de teste
Quando os testes compartilham dados, um pode afetar o outro, e o resultado depende da ordem em que são executados. Esses dois testes usam uma lista compartilhada:
store = []
def test_add():
store.append("x")
assert len(store) == 1
def test_empty():
assert store == []
| Pedido | test_add | test_empty |
|---|---|---|
| vazio e adicione | Passe | Passe |
| adicione e depois esvazie | Passe | Falha |
Nossa execução confirmou isso, e é por isso que uma suíte pode passar meses e depois quebrar quando alguém adiciona execuções paralelas ou um plugin de pedido aleatório. A cura é o isolamento:
- Forneça dados atualizados a cada teste, usando um acessório que os configura e limpa. Nosso tutorial pytest mostra como.
- Crie registros exclusivos, como um email com um sufixo aleatório, no lugar de um “usuário de teste” compartilhado.
- Envolva testes de banco de dados em uma transação e reverta-a, onde sua configuração permitir.
- Execute testes regularmente em ordem aleatória, para que esses problemas apareçam mais cedo.
Causa 3: Datas, fusos horários e aleatoriedade
Um teste que passa a semana toda pode ser reprovado no sábado, à meia-noite ou no último dia do mês. Faça do tempo uma entrada, em vez de ler o relógio dentro da sua lógica:
def is_weekend(today=None):
today = today or datetime.date.today()
return today.weekday() >= 5
is_weekend(datetime.date(2026, 11, 7)) # True (a Saturday)
is_weekend(datetime.date(2026, 11, 9)) # False (a Monday)
Esses testes dão a mesma resposta em qualquer dia. Da mesma forma, defina uma semente fixa para valores aleatórios em testes e registre-a ao usar dados aleatórios para poder reproduzir uma falha. Não assuma uma ordem específica de conjuntos, dicionários de fontes externas ou consultas de banco de dados sem ORDER BY.
Causa 4: Seletores frágeis
Um teste que encontra um botão com div:nth-child(3) > span quebra com qualquer ajuste de layout e pode falhar apenas para alguns tamanhos de tela ou variantes. Prefira localizadores baseados no que os usuários veem ou atributos de teste dedicados. Nosso guia para Localizadores de dramaturgos explica a ordem para testá-los. As animações são outra fonte frequente de problemas, então considere desativá-las em ambientes de teste.
Causa 5: Serviços e redes externas
Se um teste chamar um provedor de pagamento real ou uma API de terceiros, seu resultado dependerá do tempo de atividade e dos limites de taxa de outra pessoa. Simule essas chamadas para a maioria dos testes e mantenha um pequeno número de verificações de contrato separadas em relação ao serviço real, para que você ainda saiba se ele mudar. Nosso guia para testes de unidade versus testes de integração explica como dividi-los.
Dica: “Aprovado localmente, falha no CI” geralmente significa uma diferença de velocidade, fuso horário, recursos ou versões. A execução de testes na mesma imagem de contêiner localmente e no CI remove muitos deles. Nosso tutorial do Docker Compose pode ajudar.
Como diagnosticar um teste escamoso
- Confirme. Execute o teste várias vezes sem alterações. Com o pytest, um simples loop de shell funciona, e um plugin como
pytest-repeatfornece--count=100. - Registre evidências. Salve logs, capturas de tela e rastreamentos para cada falha. O visualizador de rastreamento do Playwright é particularmente útil aqui.
- Enfatize as condições. Execute-o com CPU limitada, em paralelo ou em ordem aleatória. Fazê-lo falhar com mais frequência torna mais fácil estudar.
- Teste sozinho. Se ele sempre passar sozinho, mas falhar no conjunto, suspeite do estado compartilhado.
- Procure padrões. Verifique quais máquinas, horários do dia e testes vizinhos estão envolvidos.
- Verifique o produto. Pergunte honestamente se isso poderia ser uma condição de corrida real em sua aplicação.
As tentativas automáticas são uma solução?
As novas tentativas são uma rede de segurança útil a curto prazo e não são uma cura. Uma única nova tentativa automática no CI pode manter o trabalho em andamento enquanto você investiga, mas um teste que precisa de uma nova tentativa para ser aprovado ainda está lhe dizendo algo. Pior ainda, um bug real e intermitente do produto que falha 20% das vezes geralmente passa por uma nova tentativa despercebido.
Use novas tentativas com estas regras: limite-as a uma ou duas, registre todos os testes que precisaram de uma e trate essa lista como sua lista de tarefas pendentes. Um teste relatado como “aprovado na nova tentativa” deve contar como um problema a ser investigado, e não como um sucesso.
Um processo de equipe para testes instáveis
- Avalie. Rastreie quais testes falham e depois passe o mesmo commit. Muitas ferramentas de CI e repórteres de teste podem mostrar isso.
- Quarentena. Retire um teste instável confirmado da execução de bloqueio, para que ele pare de atrapalhar a todos, mas continue executando-o e monitorando-o.
- Atribua um proprietário e um prazo. Uma quarentena sem prazo vira apenas um cemitério.
- Corrija ou exclua. Se não vale a pena corrigir um teste, remova-o e verifique se sua cobertura ainda existe em outro lugar.
- Prevenir. Execute novos testes várias vezes antes de mesclar. Um teste que falha uma vez em cinquenta execuções é melhor detectado agora do que em seis meses.
Trate um teste instável como um bug com prioridade, e não como ruído de fundo. A gravidade e a prioridade também se aplicam aqui, conforme abordado em gravidade versus prioridade. Se a instabilidade for generalizada, leia sobre erros mais amplos de automação de testes e considere se você tem muitos testes no nível do navegador.
Considerações finais
Os testes instáveis parecem aleatórios, mas raramente são. Quase todos se resumem a uma suposição sobre tempo, ordem, dados ou ambiente que nem sempre é verdadeira. Escolha o teste que falha com mais frequência em seu pipeline esta semana. Execute-o cinquenta vezes, encontre a suposição e corrija-a para sempre. Uma solução real ensina a uma equipe mais do que cem repetições.
Perguntas frequentes
O que é um teste instável?
Um teste instável é aprovado em algumas execuções e falha em outras, sem qualquer alteração no código. A causa pode estar no teste, no ambiente ou no produto.
O que causa testes instáveis?
As causas mais comuns são suposições de temporização, estado compartilhado entre testes, seletores instáveis, serviços externos, manipulação de datas e diferenças entre CI e máquinas locais.
Devo excluir testes instáveis?
Primeiro tente corrigi-los. Se um teste tiver baixo valor e for caro para corrigir, não há problema em excluí-lo, mas certifique-se de que sua cobertura importante exista em outro lugar.
Devo tentar novamente os testes que falharam automaticamente?
Como uma rede de segurança limitada, sim, mas registre cada nova tentativa e investigue-a. As novas tentativas por si só ocultam problemas e podem permitir que bugs intermitentes reais cheguem à produção.
Como encontro testes instáveis?
Procure testes que falham e passem no mesmo commit, usando seu histórico de CI ou uma ferramenta de relatório. Você também pode executar testes suspeitos várias vezes em um loop.
Continue aprendendo
Precisa de ajuda com testes, WordPress ou SEO?
Ajudamos as equipes a fornecer software melhor e a expandir seus sites. Conte-nos no que você está trabalhando e sugeriremos um próximo passo claro.
Mais guias sobre testes, IA e desenvolvimento moderno
Gostou deste artigo? Escolha um tópico e vá direto para nossos tutoriais mais recentes.
Explicação das alucinações de IA: por que os modelos de linguagem inventam as coisas e como reduzi-las
Explicação das alucinações de IA: o que são, por que os modelos de linguagem as produzem, tipos comuns, como reduzi-las com aterramento e instruções…
Leia o artigo →DevOpsVariáveis de ambiente explicadas: arquivos .env, segredos e configuração bem feitos
Variáveis de ambiente explicadas com exemplos de Python que executamos: leitura e configuração, por que cada valor é uma string, arquivos .env, hera…
Leia o artigo →Desenvolvimento de softwareExplicação da limitação de taxa de API: algoritmos, cabeçalhos e tratamento de erros 429
Limitação de taxa de API explicada com uma simulação que executamos: token bucket, janela fixa, janela deslizante, 429 respostas, cabeçalhos de limi…
Leia o artigo →Automação de testesZombando em Python: unittest.mock, patch e os erros que fazem os testes mentirem
Zombaria em Python com unittest.mock, testado: Mock e return_value, side_effect, patch onde um nome é usado, especificação e autospec, zombaria de d…
Leia o artigo →Teste de softwareCobertura de código explicada: cobertura de linha versus filial e o que 100% realmente significa
Cobertura de código explicada com uma execução cobertura.py real: cobertura de linha, ramal e condição, leitura de um relatório, por que 100% de cob…
Leia o artigo →Bancos de dados e SQLDELETE vs TRUNCATE vs DROP em SQL: diferenças, reversão e quando usar cada um
DELETE vs TRUNCATE vs DROP explicado com SQL testado: o que cada um remove, rollback, aciona, redefinição de identidade, velocidade e como PostgreSQ…
Leia o artigo →Bancos de dados e SQLExibições SQL explicadas: crie, use e evite as armadilhas, com exemplos
Visualizações SQL explicadas com consultas que executamos: criação de uma visualização, resultados ao vivo, ocultação de colunas, desempenho, visual…
Leia o artigo →Desenvolvimento de softwareInterface TypeScript vs tipo: diferenças reais, testadas com o compilador
Interface TypeScript versus tipo, verificada com o compilador real: o que ambos podem fazer, o que apenas um pode fazer, mesclagem de declarações, e…
Leia o artigo →Desenvolvimento de softwarePython *args e **kwargs explicados: funções flexíveis com exemplos claros
Python *args e **kwargs explicados com o código que executamos: empacotamento e descompactação, ordem dos parâmetros, argumentos somente de palavras…
Leia o artigo →Desenvolvimento de softwareFunções Python Lambda: sintaxe, usos reais e quando não usá-los
Funções lambda do Python explicadas com o código que executamos: sintaxe, classificação com chave, mapa e filtro, fechamentos, armadilha de ligação …
Leia o artigo →Inteligência artificialO que são agentes de IA? Como eles funcionam, com um loop de agente simples que você pode executar
O que são agentes de IA? Um guia simples para o loop do agente, ferramentas, memória e proteções, com um pequeno loop Python que executamos, riscos …
Leia o artigo →DevOpsMonitoramento versus observabilidade: a verdadeira diferença, com logs, métricas e rastreamentos
Explicação do monitoramento versus observabilidade: falhas conhecidas e desconhecidas, logs, métricas e rastreamentos, um passo a passo do incidente…
Leia o artigo →DevOpsDocker Volumes vs Bind Mounts: persista os dados do contêiner da maneira certa
Volumes Docker versus montagens de ligação explicadas: por que os dados do contêiner desaparecem, volumes nomeados, montagens de ligação, tmpfs, exe…
Leia o artigo →Automação de testesTDD vs BDD: principais diferenças, um recurso criado de ambas as maneiras e quando usar cada um
TDD vs BDD explicado com um recurso de desconto construído em ambos os sentidos: refatoração vermelho-verde com o código que executamos, cenários da…
Leia o artigo →Teste de softwareTeste de aceitação do usuário (UAT): processo, lista de verificação e exemplos reais
Explicação do teste de aceitação do usuário: quem o executa, como ele difere do controle de qualidade, um processo de sete etapas, um modelo de caso…
Leia o artigo →Bancos de dados e SQLValores SQL NULL explicados: IS NULL, COALESCE, NULLIF e as armadilhas a serem evitadas
Valores SQL NULL explicados com consultas que executamos: lógica de três valores, IS NULL, COALESCE, NULLIF, como agregações, junções e classificaçã…
Leia o artigo →Bancos de dados e SQLInstrução SQL CASE WHEN: sintaxe, exemplos reais e erros comuns
SQL CASE WHEN explicado com consultas que executamos: formulários simples e pesquisados, ordem de condição, pivôs com SUM, truques COUNT, classifica…
Leia o artigo →Desenvolvimento de softwareO que é JWT? JSON Web Tokens explicados com um exemplo prático
O que é JWT? JSON Web Tokens explicados: as três partes, um token construído e adulterado no código que executamos, JWT vs sessões, armazenamento, t…
Leia o artigo →Desenvolvimento de softwareLoop de eventos JavaScript explicado: pilha de chamadas, microtarefas e temporizadores com exemplos
O loop de eventos JavaScript explicado com o código que executamos: pilha de chamadas, filas de tarefas e microtarefas, por que setTimeout 0 não é i…
Leia o artigo →Desenvolvimento de softwareGeradores Python e rendimento explicado: economize memória com exemplos reais
Geradores Python e rendimento explicados com o código que executamos: como eles pausam e reiniciam, medem economia de memória, pipelines, sequências…
Leia o artigo →Inteligência artificialO que são incorporações? Similaridade de vetores e cossenos explicada de forma simples
O que são incorporações? Um guia em linguagem simples para vetores, similaridade de cossenos e pesquisa semântica, com um pequeno exemplo que execut…
Leia o artigo →DevOpsImplantação azul-verde vs canário: diferenças, compensações e como escolher
Explicação da implantação azul-verde versus canário: como cada um funciona, custo, risco e reversão, agrupamento estável de usuários que testamos, m…
Leia o artigo →Automação de testesTestes instáveis: o que os causa e como corrigi-los para sempre
Testes instáveis explicados com demonstrações que executamos: as principais causas, como confirmar e diagnosticar uma, soluções adequadas para tempo…
Leia o artigo →Teste de softwareTeste de carga versus teste de estresse: diferenças, métricas e exemplos k6
Teste de carga versus teste de estresse explicado: o que cada um prova, testes de pico e absorção, as métricas que importam, por que as médias engan…
Leia o artigo →Teste de softwareCaso de teste versus cenário de teste: principais diferenças, exemplos e quando usar cada um
Caso de teste vs cenário de teste explicado: definições claras, uma tabela lado a lado, um recurso de redefinição de senha retirado de cenários para…
Leia o artigo →Bancos de dados e SQLOtimização de consultas SQL: leia planos de execução e corrija consultas lentas, com intervalos
Otimização de consulta SQL testada em um milhão de linhas: leia planos de execução, adicione os índices corretos, corrija filtros não sargáveis, use…
Leia o artigo →Bancos de dados e SQLSQL UNION vs UNION ALL: diferenças, exemplos e desempenho testado
SQL UNION vs UNION ALL explicado com consultas que executamos: duplicatas, as regras que ambos seguem, um bug de dados real, desempenho medido, INTE…
Leia o artigo →Desenvolvimento de softwareTutorial da API JavaScript Fetch: GET, POST, tratamento de erros e tempos limite
Um tutorial da API JavaScript Fetch testado em um servidor local: GET e POST, por que 404 não lança, um auxiliar reutilizável, strings de consulta, …
Leia o artigo →Desenvolvimento de softwareManipulação de arquivos Python: leitura, gravação, CSV, JSON e pathlib com exemplos
Manipulação de arquivos Python explicada com código que executamos: modos abertos, com, leitura de arquivos grandes, CSV, JSON, pathlib, arquivos bi…
Leia o artigo →Desenvolvimento de softwareClasses e objetos Python: tutorial OOP com exemplos reais
Classes e objetos Python explicados com código que executamos: __init__, métodos, atributos de classe vs instância, herança, propriedades, métodos d…
Leia o artigo →Inteligência artificialO que é um modelo de linguagem grande? Como funcionam os LLMs, explicado sem exageros
O que é um modelo de linguagem grande? LLMs explicados em linguagem simples: tokens, incorporações, atenção, treinamento, temperatura e janelas de c…
Leia o artigo →Desenvolvimento de softwareGit merge vs rebase: diferenças, exemplos e quando usar cada um
Git merge vs rebase explicado com saída real do log, uma tabela comparativa, a regra de ouro do rebase e um fluxo de trabalho seguro para equipes.
Leia o artigo →Desenvolvimento de softwareList comprehension em Python: sintaxe, exemplos e quando evitar
List comprehension em Python explicada: sintaxe, filtros, if-else, laços aninhados, tempos medidos e quando um laço for comum é a melhor escolha.
Leia o artigo →Bancos de dados e SQLSQL JOIN explicado com exemplos (Inner, Left, Right, Full)
Entenda os joins SQL passo a passo com tabelas e consultas de exemplo: INNER, LEFT, RIGHT, FULL, CROSS e SELF, além de erros comuns e dicas de desem…
Leia o artigo →