Português

Domine TDD em JavaScript. Este guia cobre o ciclo Vermelho-Verde-Refatora, implementação com Jest e as melhores práticas do desenvolvimento moderno.

Desenvolvimento Orientado a Testes em JavaScript: Um Guia Completo para Desenvolvedores Globais

Imagine este cenário: você tem a tarefa de modificar uma parte crítica do código em um sistema legado grande. Você sente um calafrio. Sua alteração vai quebrar outra coisa? Como você pode ter certeza de que o sistema ainda funciona como esperado? Esse medo da mudança é uma doença comum no desenvolvimento de software, muitas vezes levando a um progresso lento e aplicações frágeis. Mas e se houvesse uma maneira de construir software com confiança, criando uma rede de segurança que captura erros antes mesmo de chegarem à produção? Essa é a promessa do Desenvolvimento Orientado a Testes (TDD).

O TDD não é apenas uma técnica de teste; é uma abordagem disciplinada para o design e desenvolvimento de software. Ele inverte o modelo tradicional de "escrever código, depois testar". Com o TDD, você escreve um teste que falha antes de escrever o código de produção para fazê-lo passar. Essa simples inversão tem implicações profundas na qualidade, design e manutenibilidade do código. Este guia fornecerá uma visão completa e prática da implementação do TDD em JavaScript, projetado para uma audiência global de desenvolvedores profissionais.

O que é Desenvolvimento Orientado a Testes (TDD)?

Na sua essência, o Desenvolvimento Orientado a Testes é um processo de desenvolvimento que se baseia na repetição de um ciclo de desenvolvimento muito curto. Em vez de escrever funcionalidades e depois testá-las, o TDD insiste que o teste seja escrito primeiro. Este teste inevitavelmente falhará porque a funcionalidade ainda não existe. O trabalho do desenvolvedor é então escrever o código mais simples possível para fazer aquele teste específico passar. Uma vez que ele passa, o código é limpo e melhorado. Este ciclo fundamental é conhecido como o ciclo "Vermelho-Verde-Refatora".

O Ritmo do TDD: Vermelho-Verde-Refatora

Este ciclo de três etapas é o coração do TDD. Entender e praticar este ritmo é fundamental para dominar a técnica.

Uma vez que o ciclo está completo para uma pequena parte da funcionalidade, você começa novamente com um novo teste que falha para a próxima parte.

As Três Leis do TDD

Robert C. Martin (frequentemente conhecido como "Uncle Bob"), uma figura chave no movimento de software Ágil, definiu três regras simples que codificam a disciplina do TDD:

  1. Você não deve escrever nenhum código de produção a não ser para fazer um teste de unidade que falha passar.
  2. Você não deve escrever mais de um teste de unidade do que o suficiente para falhar; e falhas de compilação são falhas.
  3. Você não deve escrever mais código de produção do que o suficiente para passar no único teste de unidade que falha.

Seguir essas leis força você a entrar no ciclo Vermelho-Verde-Refatora e garante que 100% do seu código de produção seja escrito para satisfazer um requisito específico e testado.

Por que Você Deve Adotar o TDD? O Caso de Negócios Global

Embora o TDD ofereça imensos benefícios para desenvolvedores individuais, seu verdadeiro poder é realizado no nível da equipe e do negócio, especialmente em ambientes globalmente distribuídos.

Configurando seu Ambiente de TDD em JavaScript

Para começar com o TDD em JavaScript, você precisa de algumas ferramentas. O ecossistema moderno de JavaScript oferece excelentes opções.

Componentes Principais de uma Pilha de Testes

Por sua simplicidade e natureza tudo-em-um, usaremos o Jest para nossos exemplos. É uma excelente escolha para equipes que procuram uma experiência de "configuração zero".

Configuração Passo a Passo com Jest

Vamos configurar um novo projeto para TDD.

1. Inicialize seu projeto: Abra seu terminal e crie um novo diretório de projeto.

mkdir js-tdd-project
cd js-tdd-project
npm init -y

2. Instale o Jest: Adicione o Jest ao seu projeto como uma dependência de desenvolvimento.

npm install --save-dev jest

3. Configure o script de teste: Abra seu arquivo `package.json`. Encontre a seção `"scripts"` e modifique o script `"test"`. Também é altamente recomendável adicionar um script `"test:watch"`, que é inestimável para o fluxo de trabalho TDD.

"scripts": {
  "test": "jest",
  "test:watch": "jest --watchAll"
}

A flag `--watchAll` diz ao Jest para reexecutar automaticamente os testes sempre que um arquivo for salvo. Isso fornece feedback instantâneo, o que é perfeito para o ciclo Vermelho-Verde-Refatora.

É isso! Seu ambiente está pronto. O Jest encontrará automaticamente arquivos de teste nomeados `*.test.js`, `*.spec.js`, ou localizados em um diretório `__tests__`.

TDD na Prática: Construindo um Módulo `CurrencyConverter`

Vamos aplicar o ciclo TDD a um problema prático e globalmente compreendido: converter dinheiro entre moedas. Construiremos um módulo `CurrencyConverter` passo a passo.

Iteração 1: Conversão Simples com Taxa Fixa

🔴 VERMELHO: Escreva o primeiro teste que falha

Nosso primeiro requisito é converter um valor específico de uma moeda para outra usando uma taxa fixa. Crie um novo arquivo chamado `CurrencyConverter.test.js`.

// CurrencyConverter.test.js
const CurrencyConverter = require('./CurrencyConverter');

describe('CurrencyConverter', () => {
  it('deve converter um valor de USD para EUR corretamente', () => {
    // Preparar
    const amount = 10; // 10 USD
    const expected = 9.2; // Assumindo uma taxa de câmbio fixa de 1 USD = 0,92 EUR

    // Agir
    const result = CurrencyConverter.convert(amount, 'USD', 'EUR');

    // Afirmar
    expect(result).toBe(expected);
  });
});

Agora, execute o observador de testes no seu terminal:

npm run test:watch

O teste falhará espetacularmente. O Jest reportará algo como `TypeError: Cannot read properties of undefined (reading 'convert')`. Este é o nosso estado VERMELHO. O teste falha porque `CurrencyConverter` não existe.

🟢 VERDE: Escreva o código mais simples para passar

Agora, vamos fazer o teste passar. Crie o arquivo `CurrencyConverter.js`.

// CurrencyConverter.js
const rates = {
  USD: {
    EUR: 0.92
  }
};

const CurrencyConverter = {
  convert(amount, from, to) {
    return amount * rates[from][to];
  }
};

module.exports = CurrencyConverter;

Assim que você salvar este arquivo, o Jest irá reexecutar o teste, e ele ficará VERDE. Nós escrevemos o mínimo de código absoluto para satisfazer o requisito do teste.

🔵 REFATORA: Melhore o código

O código é simples, mas já podemos pensar em melhorias. O objeto aninhado `rates` é um pouco rígido. Por enquanto, está limpo o suficiente. O mais importante é que temos uma funcionalidade funcional protegida por um teste. Vamos para o próximo requisito.

Iteração 2: Lidando com Moedas Desconhecidas

🔴 VERMELHO: Escreva um teste para uma moeda inválida

O que deveria acontecer se tentarmos converter para uma moeda que não conhecemos? Provavelmente deveria lançar um erro. Vamos definir este comportamento em um novo teste em `CurrencyConverter.test.js`.

// Em CurrencyConverter.test.js, dentro do bloco describe

it('deve lançar um erro para moedas desconhecidas', () => {
  // Preparar
  const amount = 10;

  // Agir & Afirmar
  // Nós envolvemos a chamada da função em uma arrow function para que o toThrow do Jest funcione.
  expect(() => {
    CurrencyConverter.convert(amount, 'USD', 'XYZ');
  }).toThrow('Moeda desconhecida: XYZ');
});

Salve o arquivo. O executor de testes imediatamente mostra uma nova falha. Está VERMELHO porque nosso código não lança um erro; ele tenta acessar `rates['USD']['XYZ']`, resultando em um `TypeError`. Nosso novo teste identificou corretamente essa falha.

🟢 VERDE: Faça o novo teste passar

Vamos modificar o `CurrencyConverter.js` para adicionar a validação.

// CurrencyConverter.js
const rates = {
  USD: {
    EUR: 0.92,
    GBP: 0.80
  },
  EUR: {
    USD: 1.08
  }
};

const CurrencyConverter = {
  convert(amount, from, to) {
    if (!rates[from] || !rates[from][to]) {
      // Determina qual moeda é desconhecida para uma mensagem de erro melhor
      const unknownCurrency = !rates[from] ? from : to;
      throw new Error(`Moeda desconhecida: ${unknownCurrency}`);
    }
    return amount * rates[from][to];
  }
};

module.exports = CurrencyConverter;

Salve o arquivo. Ambos os testes agora passam. Estamos de volta ao VERDE.

🔵 REFATORA: Limpe o código

Nossa função `convert` está crescendo. A lógica de validação está misturada com o cálculo. Poderíamos extrair a validação para uma função privada separada para melhorar a legibilidade, mas por enquanto, ainda é gerenciável. A chave é que temos a liberdade de fazer essas mudanças porque nossos testes nos dirão se quebrarmos alguma coisa.

Iteração 3: Busca Assíncrona de Taxas

Taxas fixas no código não são realistas. Vamos refatorar nosso módulo para buscar taxas de uma API externa (simulada).

🔴 VERMELHO: Escreva um teste assíncrono que simula uma chamada de API

Primeiro, precisamos reestruturar nosso conversor. Ele agora precisará ser uma classe que podemos instanciar, talvez com um cliente de API. Também precisaremos simular (mock) a API `fetch`. O Jest torna isso fácil.

Vamos reescrever nosso arquivo de teste para acomodar essa nova realidade assíncrona. Começaremos testando o caminho feliz novamente.

// CurrencyConverter.test.js
const CurrencyConverter = require('./CurrencyConverter');

// Simula a dependência externa
global.fetch = jest.fn();

beforeEach(() => {
  // Limpa o histórico do mock antes de cada teste
  fetch.mockClear();
});

describe('CurrencyConverter', () => {
  it('deve buscar as taxas e converter corretamente', async () => {
    // Preparar
    // Simula a resposta de sucesso da API
    fetch.mockResolvedValueOnce({
      json: () => Promise.resolve({ rates: { EUR: 0.92 } })
    });

    const converter = new CurrencyConverter('https://api.exchangerates.com');
    const amount = 10; // 10 USD

    // Agir
    const result = await converter.convert(amount, 'USD', 'EUR');

    // Afirmar
    expect(result).toBe(9.2);
    expect(fetch).toHaveBeenCalledTimes(1);
    expect(fetch).toHaveBeenCalledWith('https://api.exchangerates.com/latest?base=USD');
  });

  // Nós também adicionaríamos testes para falhas de API, etc.
});

Executar isso resultará em um mar de VERMELHO. Nosso antigo `CurrencyConverter` não é uma classe, não tem um método `async` e não usa `fetch`.

🟢 VERDE: Implemente a lógica assíncrona

Agora, vamos reescrever o `CurrencyConverter.js` para atender aos requisitos do teste.

// CurrencyConverter.js
class CurrencyConverter {
  constructor(apiUrl) {
    this.apiUrl = apiUrl;
  }

  async convert(amount, from, to) {
    const response = await fetch(`${this.apiUrl}/latest?base=${from}`);
    if (!response.ok) {
      throw new Error('Falha ao buscar as taxas de câmbio.');
    }

    const data = await response.json();
    const rate = data.rates[to];

    if (!rate) {
      throw new Error(`Moeda desconhecida: ${to}`);
    }

    // Arredondamento simples para evitar problemas de ponto flutuante nos testes
    const convertedAmount = amount * rate;
    return parseFloat(convertedAmount.toFixed(2));
  }
}

module.exports = CurrencyConverter;

Quando você salvar, o teste deve ficar VERDE. Note que também adicionamos uma lógica de arredondamento para lidar com imprecisões de ponto flutuante, um problema comum em cálculos financeiros.

🔵 REFATORA: Melhore o código assíncrono

O método `convert` está fazendo muito: buscando, tratando erros, analisando e calculando. Poderíamos refatorar isso criando uma classe `RateFetcher` separada, responsável apenas pela comunicação com a API. Nosso `CurrencyConverter` então usaria este fetcher. Isso segue o Princípio da Responsabilidade Única e torna ambas as classes mais fáceis de testar e manter. O TDD nos guia em direção a este design mais limpo.

Padrões e Antipadrões Comuns de TDD

À medida que você pratica TDD, descobrirá padrões que funcionam bem e antipadrões que causam atrito.

Bons Padrões a Seguir

Antipadrões a Evitar

TDD no Ciclo de Vida de Desenvolvimento Mais Amplo

O TDD não existe no vácuo. Ele se integra lindamente com práticas modernas de Agile e DevOps, especialmente para equipes globais.

Conclusão: Sua Jornada com TDD

O Desenvolvimento Orientado a Testes é mais do que uma estratégia de teste — é uma mudança de paradigma em como abordamos o desenvolvimento de software. Ele fomenta uma cultura de qualidade, confiança e colaboração. O ciclo Vermelho-Verde-Refatora fornece um ritmo constante que o guia em direção a um código limpo, robusto e de fácil manutenção. A suíte de testes resultante torna-se uma rede de segurança que protege sua equipe de regressões e uma documentação viva que integra novos membros.

A curva de aprendizado pode parecer íngreme, e o ritmo inicial pode parecer mais lento. Mas os dividendos a longo prazo em tempo reduzido de depuração, design de software aprimorado e aumento da confiança do desenvolvedor são imensuráveis. A jornada para dominar o TDD é de disciplina e prática.

Comece hoje. Escolha uma pequena funcionalidade não crítica em seu próximo projeto e comprometa-se com o processo. Escreva o teste primeiro. Veja-o falhar. Faça-o passar. E então, o mais importante, refatore. Experimente a confiança que vem de uma suíte de testes verde, e em breve você se perguntará como já construiu software de outra maneira.