Pular para o conteúdo

Test-Driven Development

graph LR
    RED["RED<br/>escreve teste que falha"] --> GREEN["GREEN<br/>código mínimo que passa"]
    GREEN --> REFACTOR["REFACTOR<br/>melhora o desenho, verde mantido"]
    REFACTOR --> RED
  1. Red — escreva um teste pequeno para um comportamento que ainda não existe. Ele deve falhar (e por compilar-e-falhar, não por erro de compilação bobo). O teste vermelho define a especificação do próximo passo.
  2. Green — escreva o mínimo de código de produção para passar. É permitido — e recomendado — “fingir” (retornar uma constante) para chegar ao verde rápido. O objetivo aqui é a barra verde, não a elegância.
  3. Refactor — com a rede verde protegendo, melhore o desenho: elimine duplicação, renomeie, extraia. Sem adicionar comportamento e sem sair do verde. É o elo direto com refactoring e Técnicas de Refactoring.

A cadência é rápida: dezenas de ciclos por hora. A regra de Beck é sempre manter a barra verde a poucos segundos de distância.

O ganho mais profundo do TDD não é a suíte que sobra, e sim o desenho que emerge. Escrever o teste primeiro obriga você a usar a API antes de implementá-la, do ponto de vista do cliente. Isso tem consequências arquiteturais diretas:

  • Baixo acoplamento — para testar uma unidade isolada, suas dependências precisam ser injetáveis; o design é empurrado para composição em vez de instanciação interna.
  • Aderência a DIP — as colaborações viram interfaces (portas) que o teste substitui por dublês, materializando o DIP - Dependency Inversion Principle. O código passa a depender de abstrações.
  • Interfaces enxutas — se a API é dolorosa de usar no teste, ela é dolorosa de usar em produção. A dor aparece cedo e barata.
  • Responsabilidades separadas — uma classe que exige dez mocks para ser testada está gritando violação de SRP. TDD torna o cheiro imediato.

Em suma: testabilidade e bom design são a mesma propriedade vista de dois ângulos (Testabilidade e Arquitetura). Os princípios SOLID deixam de ser teoria e viram consequência prática de fechar ciclos vermelho-verde-refatorar.

O tamanho do passo é ajustável, e essa é uma habilidade central. Quando o problema é claro, passos maiores. Quando você está incerto ou o teste anterior ficou vermelho de forma surpreendente, encurte o passo: implemente por triangulação (dois ou três exemplos que forçam a generalização) em vez de saltar direto para o algoritmo geral. Passos pequenos reduzem o tempo de depuração — se algo quebra, a causa está nas últimas poucas linhas.

Regra: números viram texto; múltiplos de 3 viram “Fizz”; de 5, “Buzz”; de ambos, “FizzBuzz”.

Ciclo 1 — Red. O caso mais simples:

class FizzBuzzTest {
FizzBuzz fizzBuzz = new FizzBuzz();
@Test
void numeroComumViraSuaString() {
assertThat(fizzBuzz.converter(1)).isEqualTo("1");
}
}

Green — o mínimo que passa (fingir é legítimo):

public class FizzBuzz {
public String converter(int n) {
return "1";
}
}

Ciclo 2 — Red (triangulação força a generalizar):

@Test
void outroNumeroComumViraSuaString() {
assertThat(fizzBuzz.converter(2)).isEqualTo("2");
}

Green — a constante não sobrevive; generaliza:

public String converter(int n) {
return String.valueOf(n);
}

Ciclos 3 e 4 — Red para Fizz e Buzz, resolvidos com if. Ciclo 5 — Red para FizzBuzz. Estado final, já com o Refactor aplicado (ordem dos casos e concatenação limpa):

public class FizzBuzz {
public String converter(int n) {
String resultado = "";
if (n % 3 == 0) resultado += "Fizz";
if (n % 5 == 0) resultado += "Buzz";
return resultado.isEmpty() ? String.valueOf(n) : resultado;
}
}

Teste final consolidado com @ParameterizedTest, expressando a especificação inteira:

class FizzBuzzTest {
FizzBuzz fizzBuzz = new FizzBuzz();
@ParameterizedTest
@CsvSource({
"1, 1",
"2, 2",
"3, Fizz",
"5, Buzz",
"15, FizzBuzz",
"30, FizzBuzz"
})
void converteConformeRegra(int entrada, String esperado) {
assertThat(fizzBuzz.converter(entrada)).isEqualTo(esperado);
}
}

Repare que o algoritmo não foi “projetado no papel”: emergiu dos casos, um por vez.

Há duas escolas sobre como isolar a unidade sob TDD, sistematizadas por Martin Fowler em Mocks Aren’t Stubs:

Classicist / Detroit Mockist / London
Isolamento Só mocka o que é caro/externo (banco, rede); usa objetos reais como colaboradores Mocka todas as dependências da classe sob teste
Foco da verificação Estado — o resultado final observável Comportamento — as interações/chamadas entre objetos
Direção do design Middle-out: desenvolve o domínio e deixa o desenho emergir Outside-in: começa pela borda e descobre colaboradores como interfaces
Risco Testes de unidade maiores, fronteira menos nítida Acoplamento a detalhes de interação; testes frágeis a refatoração

Não é religião. A escola mockista casa naturalmente com outside-in TDD e ajuda a descobrir portas (interfaces) na fronteira do domínio. A classicista produz testes mais resistentes a refactoring, porque verifica resultado e não mecânica. Na prática, a maioria dos times mistura: estilo classicista no domínio, dublês nas fronteiras de infraestrutura. A taxonomia dos dublês usados por cada estilo está em Test Doubles - Stub, Spy, Mock e Fake.

  • Espaço de solução desconhecido / spike: quando você ainda não sabe o que construir, um spike exploratório sem testes esclarece o problema; depois joga fora o spike e refaz por TDD.
  • Código de UI muito visual ou glue trivial: o custo do teste pode superar o retorno; teste o comportamento por baixo.
  • Requisitos ainda voláteis a cada minuto: escrever testes que serão jogados fora no dia seguinte é desperdício — estabilize a direção primeiro.

TDD não é obrigatório em todo lugar, mas a dificuldade de aplicá-lo geralmente denuncia um problema de design, não um defeito do TDD.

  • Escrever o teste depois e chamar de TDD: perde o efeito de design; vira só verificação retroativa.
  • Pular o Refactor: sem o terceiro passo, o código acumula a dívida dos atalhos do Green; a suíte protege exatamente para permitir a limpeza.
  • Passos grandes demais: teste que cobre muito de uma vez torna a depuração cara quando fica vermelho.
  • Testar detalhes de implementação (mockismo excessivo): testes frágeis que quebram a cada refatoração legítima.
  • Green sem Red antes: se você não viu o teste falhar, não sabe se ele testa algo.
  • Refatorar com barra vermelha: nunca; refatore só no verde.

1. Por que é essencial ver o teste falhar (Red) antes de escrever o código?

Resposta

Porque um teste que nunca foi visto falhando pode estar verde por engano (sem assert efetivo, ou testando a coisa errada). O Red confirma que o teste realmente exercita o comportamento ausente e que o Green subsequente foi mesmo o código que o satisfez.

2. Em que sentido TDD é uma técnica de design e não só de verificação?

Resposta

Escrever o teste primeiro obriga a usar a API pela ótica do cliente e a injetar dependências para isolar a unidade. Isso empurra o código para baixo acoplamento, interfaces enxutas e aderência a DIP/SRP. O desenho emerge da necessidade de tornar cada passo testável.

3. Qual a diferença central entre a escola classicista e a mockista?

Resposta

A classicista (Detroit) verifica estado e só mocka dependências caras/externas, usando objetos reais como colaboradores. A mockista (London) mocka todas as dependências e verifica comportamento (interações). A mockista favorece outside-in e descoberta de portas; a classicista gera testes mais resistentes a refactoring.

4. Por que pular o passo Refactor compromete o TDD?

Resposta

Porque o Green é obtido com o código mínimo, muitas vezes deselegante ou duplicado. O Refactor existe para pagar essa dívida enquanto a rede verde protege o comportamento. Sem ele, os atalhos acumulam e o design degrada, anulando o benefício de desenho do ciclo.