Erros Comuns em TDD que Você Deve Evitar: Guia Prático

Erros Comuns em TDD que Você Deve Evitar: Guia Prático





Erros comuns em TDD que você deve evitar


Erros comuns em TDD que você deve evitar

Guia técnico, direto ao ponto: como manter o ciclo Red-Green-Refactor fiel, com foco em qualidade, legibilidade e manutenibilidade.


1) Não seguir o ciclo Red-Green-Refactor: o erro mais comum de quem está aprendendo TDD

O TDD prega o ciclo explícito: escrever um teste que falha (vermelho), criar a mínima implementação que passe (verde) e, por fim, refatorar sem quebrar o comportamento. O problema mais frequente é pular uma dessas etapas ou encurtá-las sem verificação adequada.

  • Escrever código de produção para “apenas fazer o teste passar” sem ver o que o teste verdadeiramente exige.
  • Ignorar o estado de falha intencional do teste e já tentar adaptar a API antes de entender o requisito.
  • Refatorar código e testes simultaneamente sem validação incremental, gerando regressões sutis.

Boas práticas rápidas:

2) Testes fracos ou acoplados à implementação

Testes que dependem de detalhes da implementação (nomenclatura de métodos, estruturas internas ou classes privadas) se tornam frágeis, dificultando refatorações e desencorajando mudanças no código de produção.

Priorize testar o comportamento público do código, via API exposta, e use mocks apenas para dependências externas. Evite testar detalhes internos que podem mudar sem impactar o contrato com o consumidor.

Exemplo prático

Observação: o objetivo é demonstrar a diferença entre testar comportamento vs. implementação.

// Mau exemplo: teste que depende de implementação interna
// Descrever o que o objeto "Calculadora" faz internamente
describe("Calculadora (teste de implementação)", () => {
  it("deveria utilizar o método privado _formatResult", () => {
    const calc = new Calculadora();
    // @ts-ignore
    const formatted = calc._formatResult(5);
    expect(formatted).toBe("5.00");
  });
});

// Bom exemplo: testa apenas o comportamento público
describe("Calculadora (teste de comportamento)", () => {
  it("deveria formatar o resultado pelo método público format()", () => {
    const calc = new Calculadora();
    expect(calc.format(5)).toBe("5.00");
  });
});

Princípio aplicado: os testes devem validar o que o usuário final observa (contrato público), não como o código está implementado por trás das cenas.

3) Falta de isolamento de dependências externas

Uma armadilha comum em TDD é depender de recursos externos (banco de dados, rede, relógio do sistema) nos testes. Isso gera flakiness, lentidão e dificuldade de reproduzir falhas localmente.

  • Quando possível, injete dependências para permitir substituição por fakes/mocks durante o teste.
  • Frozen clocks para cenários de tempo determinístico; use técnicas de “time freezing” apenas quando necessário, sem comprometer o contrato de tempo do código.
  • Prefira testes de unidade com mocks de dependências, reservando testes de integração para validação de integração entre módulos críticos.

Dicas rápidas:

  • Avalie se cada teste realmente isola o comportamento desejado.
  • Considere usar fábricas de objetos de teste para criar cenários repetíveis sem depender de dados mágicos.

4) Débito de refatoração nos testes e duplicação de código

Testes mal estruturados costumam acumular duplicação de setup, asserts repetidos e dados de teste mágicos. Isso dificulta manutenção, especialmente quando o domínio evolui.

  • Utilize setup comum com beforeEach/afterEach para reduzir duplicação sem esconder a intenção de cada teste.
  • Construa dados de teste com builders/factories em vez de literais espalhados pelo suite.
  • Nomeie testes de forma explícita para refletir o comportamento esperado, não a implementação do código.

Boas práticas sugeridas:

  • Extraia helpers de teste para cenários recorrentes e documente o que cada helper representa.
  • Refatore testes junto com o código de produção, sempre verificando que a funcionalidade permanece intacta.

Exemplo de construção de dados de teste (test data builder)

// Test Data Builder em JavaScript/TypeScript
class CartBuilder {
  constructor() {
    this.items = [];
    this.taxRate = 0.0;
  }
  withItem(name, price, qty = 1) {
    this.items.push({ name, price, qty });
    return this;
  }
  withTaxRate(rate) {
    this.taxRate = rate;
    return this;
  }
  build() {
    return new Cart(this.items, this.taxRate);
  }
}

// uso no teste
describe("Cart", () => {
  it("deve calcular total com imposto", () => {
    const cart = new CartBuilder()
      .withItem("Camiseta", 50, 2)
      .withTaxRate(0.1)
      .build();
    expect(cart.total()).toBeCloseTo(110);
  });
});

Gostou do conteúdo? Aprofunde-se em mais artigos técnicos sobre qualidade de código, padrões de teste e estratégias de desenvolvimento orientado a testes. Leia outros posts no Yurideveloper para aumentar sua didática prática em TDD e design de software.

Confira outros posts



Y

Yuri Sousa

Front-End Developer / Designer

Desenvolvedor apaixonado por criar experiências digitais acessíveis e visualmente perfeitas. Escrevo sobre desenvolvimento web, design e tecnologia.