Introdução ao Teste em Java
Por que testes importam em Java, a pirâmide de testes e uma visão geral dos principais frameworks de teste em Java.
Testes automatizados são a forma de provar que o código faz o que você espera — e continuar provando isso à medida que o código muda. Em vez de executar o programa manualmente e inspecionar a saída visualmente, você escreve pequenos programas que exercitam o seu código e verificam os resultados automaticamente. Em Java, esse ecossistema é construído em torno de frameworks como JUnit e Mockito, mas as ideias fundamentais — arrange, act, assert — são simples o suficiente para escrever à mão. Este capítulo mapeia o panorama antes que os capítulos posteriores se aprofundem em cada ferramenta.
Por que testes automatizados importam
Um teste é uma verificação pequena e repetível de que um trecho de código se comporta corretamente. O benefício não está na primeira execução — está em todas as execuções seguintes. Uma vez que um comportamento é capturado em um teste, qualquer alteração que o quebre falha de forma imediata e ruidosa, em vez de aparecer como um bug em produção semanas depois. Testes também documentam a intenção: um teste bem nomeado diz o que o código deve fazer.
// A test names a behavior, runs the code, and asserts the outcome.
@Test
void addsTwoPositiveNumbers() {
int result = Calculator.add(2, 3);
assertEquals(5, result); // fails the build if result != 5
}O objetivo é feedback rápido. Um conjunto de testes verde significa que você pode refatorar com confiança; um vermelho aponta diretamente para o que quebrou.
A pirâmide de testes
Os testes são organizados em camadas, geralmente representadas como uma pirâmide. Testes unitários ficam na base: muitos deles, rápidos, cada um verificando uma classe ou método em isolamento. Testes de integração ficam no meio: menos, mais lentos, verificando que os componentes funcionam juntos (seu código mais um banco de dados, por exemplo). Testes end-to-end (E2E) ficam no topo: poucos, os mais lentos, conduzindo a aplicação inteira como um usuário faria.
| Nível | Escopo | Velocidade | Quantidade | Ferramentas Java |
|---|---|---|---|---|
| Unitário | uma classe/método | rápido (ms) | muitos | JUnit, AssertJ |
| Integração | vários componentes | médio | alguns | JUnit, Testcontainers |
| End-to-end | sistema inteiro | lento | poucos | Selenium, REST-assured |
A forma importa: apoie-se nos testes unitários baratos e rápidos para a maior parte da cobertura, e reserve os testes E2E lentos e frágeis para um punhado de jornadas críticas do usuário.
O padrão arrange–act–assert
Quase todo teste, em qualquer framework, segue a mesma estrutura de três etapas. Arrange (preparar) as entradas e quaisquer dependências. Act (agir) chamando o código sob teste. Assert (verificar) que o resultado corresponde ao que você espera. Manter essas etapas visualmente separadas torna um teste fácil de ler e fácil de depurar quando falha.
@Test
void rejectsBlankUsername() {
// Arrange
UserService service = new UserService();
// Act
boolean valid = service.isValidUsername(" ");
// Assert
assertFalse(valid);
}Uma asserção que falha lança uma exceção, o framework a registra, e a execução continua para o próximo teste — assim, um comportamento quebrado nunca oculta os outros.
JUnit, o executor padrão
JUnit é o framework de testes unitários de facto para Java. Você anota métodos com @Test, o JUnit os descobre por reflexão, executa cada um e reporta aprovação/falha. Asserções como assertEquals, assertTrue e assertThrows são auxiliares estáticos que reprovam o teste quando a expectativa não é atendida. Projetos reais executam o JUnit através de uma ferramenta de build (o plugin Surefire do Maven ou a tarefa test do Gradle), não manualmente.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void dividesNumbers() {
assertEquals(4, Calculator.divide(8, 2));
}
@Test
void throwsOnDivideByZero() {
assertThrows(ArithmeticException.class, () -> Calculator.divide(1, 0));
}
}Como não há JAR do JUnit nem ferramenta de build neste executor, o exemplo abaixo constrói a mesma ideia do zero — um pequeno harness que executa verificações nomeadas e contabiliza aprovações e falhas, exatamente o que @Test mais assertEquals fazem internamente.
O que observar na execução:
- Cada chamada a
assertEqualsé um caso de teste — prepara as entradas, age chamandoaddouisBlank, e verifica o resultado — espelhando exatamente o que um método@Testdo JUnit faz. - Uma verificação aprovada imprime
PASSe uma reprovada imprimeFAILcom os valores esperado e atual, que é o diagnóstico que as mensagens de asserção do JUnit fornecem. - O caso intencionalmente errado (
expected 10 but got 5) mostra como é um teste vermelho: o harness continua executando as verificações restantes em vez de parar na primeira falha. - O resumo contabiliza 5 no total, 4 aprovados, 1 reprovado — o mesmo relatório de aprovação/falha que um executor de testes imprime ao final de uma execução.
- Como um teste falhou, o programa termina com
BUILD FAILURE, demonstrando por que um único teste quebrado deve quebrar o build inteiro na CI.
Como as peças se encaixam
As ferramentas de teste do Java se sobrepõem umas às outras, desde asserções brutas até integração completa com o build:
- Asserções (
assertEquals,assertThrows) declaram o que deve ser verdadeiro. - JUnit descobre e executa métodos
@Teste reporta os resultados. - Mockito fornece colaboradores falsos para que uma unidade possa ser testada em isolamento.
- Maven ou Gradle integra o conjunto de testes ao build, reprovando-o em qualquer teste vermelho.
- CI executa o build a cada push, para que código quebrado nunca chegue à branch principal.
Cada capítulo posterior trata de um degrau dessa escada — anotações e asserções do JUnit primeiro, depois mocking com Mockito, e depois integração dos testes com Maven e Gradle. Entender onde cada ferramenta se encaixa mantém toda a história de testes coerente.