ConstructiCat Logo
CodeBust.
Browse section ▾

Sleepy Test.

Um Sleepy Test pausa a execução com um atraso fixo no código (`Thread.sleep`, `setTimeout`, `cy.wait(2000)`, `page.waitForTimeout`) para esperar por um trabalho assíncrono em vez de esperar pela condição real.

##Signs and Symptoms

Você vê um atraso com número mágico posicionado entre uma ação e sua asserção, com um comentário se desculpando por ele:

test('order is processed', async () => {
  submitOrder(order);

  // dá tempo para o worker terminar
  await new Promise((r) => setTimeout(r, 2000));

  expect(await getOrderStatus(order.id)).toBe('processed');
});

Sinais reveladores:

  • Sleeps fixos no corpo dos testes ou em hooks: Thread.sleep(500), await sleep(1000), time.sleep(2), cy.wait(3000), await page.waitForTimeout(2000), browser.pause(2000).
  • Comentários como // espera a animação, // deixa o banco se atualizar, // instável sem isto.
  • A duração do sleep cresce ao longo do tempo (1000 vira 2000 vira 5000) à medida que as pessoas combatem falhas intermitentes acolchoando o atraso.
  • O número é a única sincronização — não há asserção, polling ou evento ao qual a espera esteja de fato atrelada.
  • Uma suíte lenta cujo tempo de relógio é dominado por sleeps em vez de trabalho real.

O smell é sobre espera incondicional. cy.wait('@apiAlias') ou await expect(locator).toBeVisible() esperam por uma condição e estão ok; cy.wait(2000) e page.waitForTimeout(2000) esperam pelo relógio e não estão.

##Reasons for the Problem

Por que acontece

  • Uma operação assíncrona (uma fila, um timer, uma animação, uma chamada de rede, uma thread em segundo plano) não tem um gancho óbvio em que esperar, então um atraso é o caminho de menor resistência.
  • Um teste estava falhando de forma intermitente e alguém o "consertou" inserindo ou aumentando um sleep até ele ficar verde na máquina dessa pessoa.
  • O teste interage com um sistema externo real (relógio, agendador, sistema de arquivos, serviço HTTP) cuja conclusão não é observável, então o tempo é adivinhado.

Por que prejudica

  • Confiabilidade / falsa confiança. Um sleep codifica uma suposição — "o trabalho termina em N ms." O tempo de processamento varia entre máquinas, carga de CI e execuções, então o teste é não determinístico: passa localmente e falha em um agente de CI sobrecarregado, ou pior, o sleep é curto demais e a asserção corre contra o código, passando apenas por sorte de tempo. Essa é uma das causas-raiz mais citadas de testes instáveis.
  • Lentidão. Um sleep fixo sempre espera a duração completa, mesmo quando o trabalho terminou em 20 ms. Multiplicados por toda uma suíte, os sleeps transformam segundos de trabalho real em minutos de espera ociosa, o que desestimula rodar os testes com frequência.
  • Manutenibilidade. O número mágico é frágil: acelerar ou desacelerar o sistema sob teste quebra silenciosamente o contrato de tempo, e a única "correção" à qual as pessoas recorrem é aumentar o número — uma catraca que deixa a suíte mais lenta e ainda assim instável.
  • Legibilidade. O atraso esconde o que o teste está de fato esperando. Um leitor não consegue dizer se sleep(2000) protege uma escrita no banco, uma renderização ou nada, então a intenção do teste fica obscurecida.

##Treatment

Substitua "esperar um tempo fixo" por "esperar pela condição." Identifique o sinal observável de que o trabalho assíncrono terminou e, então, bloqueie nele com um timeout generoso.

  1. Faça polling pela condição. Use um helper de polling/retry — Awaitility (JVM), vi.waitFor / waitFor (Vitest, Testing Library), o waitFor do Jest ou as asserções com retry automático do seu executor de testes — para que o teste prossiga no instante em que a condição for verdadeira e só falhe após um timeout.
  2. Prefira asserções aguardadas nativas. Ferramentas de E2E web já fazem retry: as asserções web-first do Playwright (await expect(locator).toBeVisible()) e os comandos com retry automático do Cypress eliminam totalmente a necessidade de esperar.
  3. Espere por eventos, não pelo relógio. Aguarde a promise, faça await da resposta de rede, ou use um CountDownLatch/callback/waitFor('@alias') que o código de produção de fato sinaliza.
  4. Controle o tempo em vez de gastá-lo. Quando o atraso for um timer real no código sob teste, use fake timers (jest.useFakeTimers(), vi.useFakeTimers(), vi.advanceTimersByTimeAsync) para avançar o relógio de forma determinística em vez de dormir.

Antes:

test('order is processed', async () => {
  submitOrder(order);
  await new Promise((r) => setTimeout(r, 2000)); // sleepy
  expect(await getOrderStatus(order.id)).toBe('processed');
});

Depois (faça polling pela condição com um timeout limitado):

import { vi } from 'vitest';

test('order is processed', async () => {
  submitOrder(order);
  await vi.waitFor(
    async () => expect(await getOrderStatus(order.id)).toBe('processed'),
    { timeout: 5000, interval: 50 },
  );
});

Equivalentes em Playwright/Cypress:

// Playwright — a asserção web-first faz retry automático até ficar visível ou estourar o timeout
await expect(page.getByText('processed')).toBeVisible();

// Cypress — espere pela requisição, não por um número
cy.intercept('POST', '/orders').as('createOrder');
cy.wait('@createOrder');

O resultado não espera mais do que o necessário, falha rapidamente com uma mensagem de timeout clara e não depende mais da velocidade da máquina.

##Detected by