Projetos Práticos para Aprender TDD: Guia Completo de Test-Driven Development

Projetos Práticos para Aprender TDD: Guia Completo de Test-Driven Development






Projetos práticos para aprender TDD


Projetos práticos para aprender TDD

Guia técnico e prático apresentando ciclos Red-Green-Refactor com atividades reais. Este post corresponde ao tema “projetos-praticos-para-aprender-tdd.md”.


1) Conceito e ciclo do TDD: Red, Green e Refactor

O Test-Driven Development (TDD) orienta o desenvolvimento pela verificação de requisitos por meio de testes, antes da implementação completa. O ciclo padrão é simples e poderoso:

  • Red: escrevo um teste que falha porque a funcionalidade ainda não existe.
  • Green: implemento o mínimo código para fazer o teste passar.
  • Refactor: melhoro o código sem alterar o comportamento externo, mantendo os testes verdes.

Boas práticas para avançar com TDD:

2) Projeto prático 1: Calculadora de operações básicas com TDD

Objetivo: validar uma API de calculadora com operações simples (adição, subtração, multiplicação, divisão) através de testes antes da implementação completa. O foco é demonstrar o ciclo Red-Green-Refactor com código mínimo e claro.

Plano de teste inicial (Red):

  • Calculadora.add(2, 3) deve retornar 5.
  • Calculadora.sub(5, 2) deve retornar 3.
  • Calculadora.mul(4, 3) deve retornar 12.
  • Calculadora.div(10, 2) deve retornar 5.

Bloco de código demonstrativo (teste + implementação mínima) — mantém o foco no ciclo TDD:

// tests/calculator.test.js
describe('Calculadora', () => {
  it('deve somar dois números', () => {
    expect(Calculator.add(2, 3)).toBe(5);
  });

  it('deve subtrair dois números', () => {
    expect(Calculator.sub(5, 2)).toBe(3);
  });

  it('deve multiplicar dois números', () => {
    expect(Calculator.mul(4, 3)).toBe(12);
  });

  it('deve dividir dois números', () => {
    expect(Calculator.div(10, 2)).toBe(5);
  });
});

// src/calculator.js (implementação mínima para passar os testes)
export const Calculator = {
  add(a, b) { return a + b; },
  sub(a, b) { return a - b; },
  mul(a, b) { return a * b; },
  div(a, b) { 
    if (b === 0) throw new Error('Divisão por zero');
    return a / b; 
  }
};

Observação: após fazer os testes passarem (Green), reviso o código para refatorar, sem modificar a interface externa. Se desejar, posso detalhar mais casos de borda, como números negativos, frações ou entradas não numéricas, em projetos subsequentes.

3) Projeto prático 2: Gerenciador de tarefas (To-Do) com TDD

Este segundo projeto expande para práticas de modelagem de domínio, gestão de estado simples e validação de regras de negócio. Objetivo: criar um conjunto de testes que guiem a implementação de uma lista de tarefas leve e confiável.

  • Adicionar tarefa com título obrigatório.
  • Marcar tarefa como concluída.
  • Listar tarefas pendentes e concluídas.
  • Não permitir título vazio.

Esboço de código de alto nível (padrões de TDD):

// Testes descritos (pseudo-código)
describe('TaskManager', () => {
  it('adiciona uma tarefa com título', () => {
    const tm = new TaskManager();
    tm.addTask('Estudar TDD');
    expect(tm.count()).toBe(1);
  });

  it('conclui uma tarefa', () => {
    const tm = new TaskManager();
    tm.addTask('Escrever post');
    tm.completeTask(0);
    expect(tm.isDone(0)).toBe(true);
  });

  it('valida título não vazio', () => {
    const tm = new TaskManager();
    expect(() => tm.addTask('')).toThrow();
  });
});

Ideias de implementação para o comportamento mínimo (também em geral, não prescritivo):

  • Classe Task com propriedades: id, title, done.
  • TaskManager com métodos: addTask(title), completeTask(id), count(), isDone(id).

4) Projeto prático 3: Cadastro simples com validação de domínio

Neste último módulo, exploramos validação de entradas pela ótica de domínio, definindo regras claras antes de construir a camada de serviço. Objetivo: demostrar como os testes orientam as regras de negócio de forma previsível.

  • Email deve ter formato válido (ex.: usuario@dominio.com).
  • Senha mínima de 8 caracteres com pelo menos uma letra e um número.
  • Nome de usuário único no domínio (simulação simples).

Exemplo de caso de teste em alto nível:

// Testes de validação (alto nível)
describe('UserRegistration', () => {
  it('aceita email válido', () => {
    expect(isValidEmail('joao@exemplo.com')).toBe(true);
  });

  it('rejeita senha fraca', () => {
    expect(isStrongPassword('1234')).toBe(false);
  });

  it('rejeita usuário duplicado', () => {
    const reg = new UserRegistration();
    reg.register('ana', 'ana@example.com', 'Senha123');
    expect(() => reg.register('ana', 'ana2@example.com', 'Senha123')).toThrow();
  });
});

Notas úteis para este projeto:

  • Defina interfaces de domínio simples antes de expor APIs públicas.
  • Escreva testes que cubram casos positivos e negativos, assegurando consistência de feedback ao usuário.
  • Considere validações em uma camada de serviço, mantendo a validação de regras no domínio puro.

Observação de prática

Os blocos de código acima ilustram a prática de escrever testes antes da implementação completa e de manter o foco na API pública. Adaptar os exemplos para a sua pilha (JS/TS, Python, Java, etc.) é direto: alinhe os nomes das funções aos padrões da sua codebase e ajuste os asserts conforme o framework de testes utilizado.

Gostou? Continue explorando mais conteúdos técnicos

Leve seu conhecimento adiante lendo outros posts do Yurideveloper com foco em qualidade de código, TDD e boas práticas de desenvolvimento.

Ler: Introdução ao TDD
Ler: Testes e Boas Práticas



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.