af aprenda frontend
módulo 07 qualidade

Por que escrever testes — confiança para mudar.

Confiança para mudar código, refactor seguro, documentação executável, captura de regressões. O que testes resolvem e o que não.

O módulo de React terminou com o blog funcionando — mas testado apenas manualmente. Toda vez que você muda o LikeButton, precisa abrir o browser, carregar a página, encontrar um artigo, clicar no botão e verificar que a contagem mudou. Se você mudou utils.ts ontem e esqueceu que formatarData também é usada no ArticleCard, o bug só aparece quando alguém reclamar. Testes automatizados existem para que a máquina faça essa verificação — cada vez, toda vez, em segundos.

O que testes realmente resolvem

O valor principal de testes não é “provar que o código funciona”. É confiança para mudar código.

Imagine que você precisa refatorar calcularTempoLeitura para usar uma lógica diferente de contagem de palavras. Sem testes, você faz a mudança, reza, e espera que nada quebre. Com testes, você roda a suite e em 300 milissegundos sabe se os casos cobertos ainda passam:

ts
import { describe, it, expect } from "vitest";
import { calcularTempoLeitura } from "./utils";

describe("calcularTempoLeitura", () => {
  it("retorna 1 para textos de até 200 palavras", () => {
    const texto = "palavra ".repeat(150).trim();
    expect(calcularTempoLeitura(texto)).toBe(1);
  });

  it("retorna 2 para textos entre 201 e 400 palavras", () => {
    const texto = "palavra ".repeat(250).trim();
    expect(calcularTempoLeitura(texto)).toBe(2);
  });

  it("retorna 1 para string vazia", () => {
    expect(calcularTempoLeitura("")).toBe(1);
  });
});
calcularTempoLeitura.test.ts — testes que dão segurança para refatorar.

Quando você troca a lógica interna e os testes continuam passando, o comportamento público está preservado. Quando um teste quebra, você sabe exatamente o que mudou — antes de fazer commit.

Regressões — quando algo que funcionava para de funcionar

Regressões são mudanças que quebram código existente — frequentemente em partes que parecem não ter relação com a mudança feita. Um exemplo concreto do blog:

ts
// Você adiciona suporte a hífens compostos em gerarSlug:
export function gerarSlug(texto: string): string {
  return texto
    .toLowerCase()
    .normalize("NFD")
    .replace(/[̀-ͯ]/g, "")  // remove acentos
    .replace(/[^a-z0-9\s-]/g, "")     // mantém apenas alfanumérico e hífen
    .replace(/\s+/g, "-")
    .trim();
}

// O que você não percebeu: o caractere '#' que antes ficava como '-hash'
// agora é removido completamente. gerarSlug("C#") retornava "c-hash",
// agora retorna "c". Artigos com "C#" no título ficam com slug errado.
Uma mudança inocente em gerarSlug que quebra outro comportamento.

Sem testes, essa regressão vai para produção e aparece como um bug reportado dias depois, difícil de rastrear. Com um teste para o caso "C#", o problema aparece no momento da mudança:

ts
it("remove caracteres especiais mas mantém letras e números", () => {
  expect(gerarSlug("C# em 2024")).toBe("c-em-2024");
  // falha na nova versão — avisa antes do commit
});
Teste que captura a regressão imediatamente.

Documentação executável

Um teste bem nomeado descreve o comportamento esperado de forma que pode ser verificada automaticamente. Diferente de um comentário no código — que fica desatualizado sem aviso —, um teste falha quando o comportamento muda:

ts
describe("formatarData", () => {
  it("retorna 'agora' para datas dos últimos 60 segundos", () => {
    const agora = new Date();
    expect(formatarData(agora)).toBe("agora");
  });

  it("retorna 'há X minutos' para datas de menos de uma hora", () => {
    const vinteMinutosAtras = new Date(Date.now() - 20 * 60 * 1000);
    expect(formatarData(vinteMinutosAtras)).toBe("há 20 minutos");
  });

  it("retorna data formatada por extenso para datas com mais de 7 dias", () => {
    const antigamente = new Date("2024-01-15");
    expect(formatarData(antigamente)).toBe("15 de jan. de 2024");
  });
});
Testes como especificação legível de formatarData.

Ler essa suite é ler a especificação de formatarData. Qualquer pessoa nova no projeto entende o contrato da função sem precisar lê-la internamente. E se a implementação mudar de forma incompatível com o contrato, um teste quebra e avisa.

O que testes não resolvem

Testes têm limites importantes. Entender esses limites evita a ilusão de que “tem testes, então está correto”.

Lógica de negócio errada. Se o requisito está errado, o teste do requisito errado passa. Se o cliente queria calcularTempoLeitura com 250 palavras por minuto e você implementou e testou com 200, os testes passam — mas o produto está errado.

Casos que você não imaginou. Cobertura de 100% não significa que todos os cenários foram testados — significa que todas as linhas foram executadas ao menos uma vez. Uma linha executada não garante que todos os inputs possíveis produzem o resultado correto.

Bugs visuais. Testes verificam comportamento e estrutura do DOM — não que o botão está na posição certa, que a cor é exatamente #61dafb, ou que o layout não quebra em 375px.

O custo zero. Testes têm custo de escrita e manutenção. Testes mal escritos quebram a cada refactor, emitem avisos que não importam e criam resistência a mudanças. A próxima lição mostra como estruturá-los para evitar isso.

O custo de não testar

O custo de não ter testes é acumulado — não aparece no sprint atual. Aparece meses depois, quando o código cresceu e ninguém mais sabe o que pode mudar com segurança.

O sintoma mais claro: medo de mexer em código antigo. “Esse módulo ninguém toca — está funcionando, não vamos arriscar.” Isso é código legado — não porque é velho, mas porque não há testes para dar confiança de mudança.

O ciclo sem testes: mudança é feita → vai para produção → usuário reporta bug → você debuga → corrige → repete. Com testes: mudança é feita → suite falha → você corrige antes do push → vai para produção sem o bug.

Resumo

  • O principal benefício dos testes é confiança para mudar código — refatorar, adicionar funcionalidades e corrigir bugs sem medo de regressões silenciosas.
  • Regressão é quando algo que funcionava para de funcionar após uma mudança. Testes capturam regressões antes de chegar à produção.
  • Documentação executável: testes bem nomeados descrevem o comportamento esperado e falham quando ele muda — diferente de comentários que ficam desatualizados.
  • Limites: testes não provam ausência de bugs, não cobrem requisitos errados, não veem o visual.
  • Custo de não testar: medo de mudar código, regressões em produção, ciclo de debug lento.

A próxima aula apresenta os três tipos de teste — unitário, integração e end-to-end — e quando cada um faz sentido no contexto do blog.

/ checkpoint verifique seu entendimento
questão 1 de 4

Qual é o principal benefício prático de ter testes automatizados?