FNSP Web Page

Testes que falham de vez em quando são piores do que nenhum

Um teste que falha de vez em quando parece um mal menor: corre-se outra vez e passa. O custo real não é o tempo perdido — é o que ele ensina. Ao fim de duas semanas, a equipa aprendeu que o vermelho não quer dizer nada, e a partir daí a bateria de testes deixou de ser um sinal para passar a ser ruído.

É por isso que são piores do que não ter teste nenhum. Sem testes, ninguém confia; com testes intermitentes, confia-se e não se devia.

1. De onde vêm, por ordem de frequência

Esperar por tempo em vez de esperar por uma condição. É a primeira de todas, e a mais fácil de corrigir:

// Não: 200 ms é um palpite sobre a máquina de quem corre
await esperar(200);
assert.ok(elemento.visivel);

// Sim: espera pelo que interessa, e desiste ao fim de um limite
await ateQue(() => elemento.visivel, { limite: 2000 });

Um número de milissegundos escrito à mão acerta na máquina de quem o escreveu e falha na integração contínua, que é mais lenta e está sempre com carga.

Ordem e estado partilhado. Dois testes que escrevem na mesma tabela, no mesmo ficheiro ou na mesma variável de módulo. Passam na ordem por que foram escritos e falham no dia em que o executor decide paralelizar.

Datas de fronteira. O teste que passa o ano inteiro e falha a 31 de dezembro, ou às 23:59, ou num ano bissexto. Aparece uma vez e desaparece antes de alguém o reproduzir.

A rede. Se um teste fala com o mundo, um dia o mundo não responde.

2. Apanhá-lo antes que ele apanhe

Um teste intermitente não se encontra a correr a bateria uma vez. Encontra-se a forçar as condições que o fazem falhar:

# Repetir o mesmo teste muitas vezes seguidas
for i in $(seq 1 100); do npm test -- o-suspeito || break; done

# E correr tudo em paralelo, que é onde o estado partilhado aparece
node --test --experimental-test-isolation=none

Se o executor souber baralhar a ordem, ainda melhor. A maior parte dos casos de estado partilhado cai à primeira mudança de ordem.

3. O que fazer quando aparece um

Há três reações possíveis, e uma delas é má:

  • Corrigir — o ideal, quando a causa é óbvia.

  • Pôr de quarentena — marcá-lo como saltado, com um registo aberto e um prazo. Feio, mas honesto: toda a gente sabe que aquilo está por resolver.

  • Repetir automaticamente até passar — esta é a má, e é a mais usada.

A repetição automática não conserta nada; esconde. E esconde a coisa errada: um teste intermitente é, muitas vezes, um produto intermitente. Uma condição de corrida que falha num teste em cada cinquenta vai falhar em produção em cada cinquenta utilizadores — só que ali ninguém volta a tentar.

4. O que os torna impossíveis

Fui ver a bateria deste site à procura de intermitentes e não há nenhum — mas isso não é mérito, é consequência de onde a lógica ficou:

contarPalavras(html)        // mesmo texto, mesmo número
realcarCodigo(html)        // mesmo código, mesmas marcas
tomMaisLivre([0, 30, 60])  // mesma lista, mesmo tom

Não há relógio, não há rede, não há ordem, não há nada partilhado. Corram na sequência que correrem, dão o mesmo resultado.

Onde isso não for possível — e nem sempre é —, as duas regras que mais rendem são: cada teste cria o que precisa e limpa atrás de si (uma transação com rollback no fim resolve o caso da base), e o que vem de fora entra como argumento, incluindo o relógio.

O resumo

Um teste que falha de vez em quando está a dizer alguma coisa. Quase sempre é «há aqui uma corrida», e a corrida também existe no produto. Repeti-lo até passar é mandar calar a única parte do sistema que reparou no problema.

Comentários

Ainda ninguém comentou este artigo.

Voltar ao blog

Gostávamos de saber quantas pessoas visitam o site, com o Google Analytics. Sem a sua autorização não corre nada. Política de Privacidade.