Todo bug corrigido precisa virar teste de regressão?

Um bug escapou, alguém passou horas reproduzindo, a correção entrou e a pergunta aparece no review: “cadê o teste?”. A cobrança faz sentido. Um teste de regressão transforma a falha descoberta em memória executável do projeto. Se o mesmo comportamento quebrar de novo, a equipe descobre antes do usuário.

Mas daí até exigir um teste exclusivo para cada ticket existe uma distância. Nem todo defeito tem o mesmo risco, nem toda reprodução cabe bem numa suíte automatizada e nem sempre a melhor proteção está no nível em que o bug apareceu. O objetivo não é pagar uma multa em linhas de teste. É impedir que um comportamento importante volte a falhar.

O melhor caso é o teste que fica vermelho antes da correção

Quando possível, primeiro reproduza a falha com um teste. Ele deve falhar no código atual pela razão esperada. Depois aplique a correção e veja o mesmo teste passar. Esse pequeno ritual prova duas coisas: a suíte realmente captura o problema e o patch realmente muda o comportamento.

Imagine uma função que aceitava desconto acima do subtotal e devolvia um valor negativo. Um teste direto pode registrar o contrato sem conhecer a implementação:

import { describe, expect, it } from 'vitest';
import { calcularTotal } from './calcularTotal';

describe('calcularTotal', () => {
  it('nunca devolve um total negativo', () => {
    expect(calcularTotal({ subtotal: 80, desconto: 100 })).toBe(0);
  });
});

O teste fala do resultado que o sistema promete, não do Math.max usado no patch. Se a implementação mudar amanhã, a proteção continua válida. Esse é o tipo de regressão barato de manter e fácil de entender seis meses depois.

Quando a resposta tende a ser “sim”

Alguns sinais tornam a cobertura quase obrigatória. O primeiro é o bug ter chegado à produção ou escapado repetidamente de revisão e QA. O segundo é afetar um contrato importante: cobrança, permissão, autenticação, perda de dados, compatibilidade de API ou uma jornada central do produto.

  • a causa envolve uma condição de borda fácil de reintroduzir;
  • a correção mexe em código compartilhado ou difícil de raciocinar;
  • há várias combinações de entrada e uma delas ficou esquecida;
  • o bug representa uma família de problemas, não apenas um valor isolado;
  • a falha pode voltar sem produzir um erro visível de imediato.

Nesses casos, o teste não serve apenas para guardar o patch. Ele revela um pedaço do domínio que a suíte não estava descrevendo. Às vezes a melhor resposta é ampliar uma tabela de casos ou uma propriedade geral, em vez de criar um teste com o número do chamado no título.

O teste certo pode estar em outro nível

Um problema encontrado na interface não exige automaticamente um teste end-to-end. Se a causa está numa função pura de cálculo, o teste unitário costuma ser mais rápido, determinístico e preciso. Se nasceu na integração entre aplicação e banco, um teste de integração pode enxergar o que mocks escondem. Reserve o navegador para aquilo que depende mesmo do fluxo completo.

A regra útil é escolher o nível mais baixo que reproduza a falha com fidelidade. Baixo demais vira uma simulação que nunca quebraria como produção. Alto demais cria uma suíte lenta e sensível a detalhes que não têm relação com o defeito.

Quando um teste novo pode não se pagar

Há exceções honestas. Um erro de texto corrigido num conteúdo estático, uma peculiaridade visual já coberta por comparação de screenshots ou uma falha causada por infraestrutura externa impossível de reproduzir de forma estável podem não justificar um caso novo. Às vezes o teste existente estava errado e deve ser ajustado; às vezes a prevenção correta é validação de schema, lint, tipo mais forte, monitoramento ou alerta.

Também existe o teste tão acoplado à implementação que custa mais do que protege. Se ele precisa simular vinte dependências privadas para afirmar uma linha interna, provavelmente está congelando o código, não o comportamento. Nessa situação, vale procurar uma fronteira melhor ou reconhecer no review por que outra barreira foi escolhida.

Use risco como critério, não culpa

Uma política saudável pode ser simples: todo bug relevante deve provocar uma conversa sobre prevenção. Para falhas de alto risco, um teste automatizado que falha antes do patch é a opção padrão. Para as demais, o pull request registra qual proteção foi adicionada ou por que o custo de automatizar não compensa.

Isso evita dois extremos: deixar a base esquecer problemas caros e transformar a suíte num arquivo morto de incidentes triviais. O teste de regressão tem valor quando aumenta a confiança para mudar o sistema. A contagem de testes, sozinha, não mede isso.

Fontes