Teste é um código que verifica se outro código funciona. Quando o teste passa, você sabe que a função continua fazendo o que deveria. Quando uma mudança quebra alguma coisa em outro canto, o teste avisa na hora — não na mão do usuário.
Um teste é um pedaço de código que chama outro código e verifica o resultado. Se o resultado for o esperado, passa. Se não for, falha — e o teste te diz o que esperava e o que veio.
Exemplo simples: uma função soma(a, b). O teste chama soma(2, 3) e verifica se voltou 5.
// código que vai ser testado
public int soma(int a, int b) {
return a + b;
}
// código de teste
@Test
public void somaDeveRetornarSomaDosDois() {
int resultado = soma(2, 3);
assertEquals(5, resultado);
}
Um teste é só isso: chamar e comparar.
Regressão é quando uma mudança nova quebra algo que já funcionava. Você arruma o bug em A — e sem perceber quebra B, que dependia de A do jeito antigo.
Sem teste, você descobre quando o usuário reclama (no pior momento). Com teste, ele falha no segundo em que você salva — porque toda vez que o código muda, todos os testes rodam.
| Sem testes | Com testes |
|---|---|
| Quebrou e ninguém viu | Quebrou e o teste apita |
| Bug aparece em produção | Bug aparece antes do commit |
| Refatorar dá medo | Refatorar é seguro |
O ganho não é só "achar bugs". É mexer no código sem medo — porque se você quebrar algo, vai saber.
TDD é Test-Driven Development — desenvolvimento guiado por testes. A ordem é invertida: primeiro o teste, depois o código que faz o teste passar.
O ciclo tem 3 passos, repetidos uma feature de cada vez:
| Passo | O que você faz | Estado |
|---|---|---|
| 1. Red | Escreve o teste do código que ainda não existe. Roda. Ele falha. | 🔴 vermelho |
| 2. Green | Escreve o código mínimo que faz o teste passar. Roda. Passa. | 🟢 verde |
| 3. Refactor | Limpa o código (nomes, duplicação) sem quebrar o teste. | 🟢 verde |
Depois do Refactor, volta pro Red da próxima feature. Ciclo de novo.
À primeira vista parece estranho: como testar algo que não existe? Mas é justamente esse o ponto.
Escrever o teste primeiro te força a decidir o comportamento antes da implementação:
Você desenha a interface (como o código vai ser usado) antes de pensar em como resolver. E só implementa o que o teste pediu — sem código sobrando, sem feature inventada.
| Sem TDD | Com TDD |
|---|---|
| Implementa primeiro, testa depois (ou nunca) | Testa primeiro, implementa depois |
| Tende a sobrar código "pra todo caso" | Só existe o que o teste pediu |
| Interface descoberta no meio do caminho | Interface decidida antes |
Vamos rodar o ciclo Red → Green → Refactor numa função soma(a, b). Ela ainda não existe.
O código abaixo nem compila ainda — a função soma não existe. É proposital: o teste descreve o que deveria existir.
@Test
public void somaDeveRetornarSomaDosDois() {
int resultado = soma(2, 3);
assertEquals(5, resultado);
}
Roda → falha (não compila ou o assert quebra). 🔴
Agora escreve só o necessário pra esse teste passar. Nada além disso.
public int soma(int a, int b) {
return a + b;
}
Roda → passa. 🟢
Olha o código: nome bom, sem duplicação, sem lógica complicada. Não tem nada a melhorar nesse ciclo. Pula pro próximo Red.
Você lembra de um caso novo: soma(-1, 1). Escreve o teste, roda. Já passa? Ótimo, segue. Falha? Volta pro Green.
@Test
public void somaDeveLidarComNegativos() {
assertEquals(0, soma(-1, 1));
}
É assim que o código cresce: um caso de cada vez, sempre com teste do lado.