Ciclo de Vida dos Testes JUnit no Java
Ciclo de vida de instâncias de teste e comportamento por método vs. por classe no JUnit 5.
Todo teste JUnit 5 é executado dentro de um ciclo de vida bem definido: uma sequência de hooks de configuração e limpeza que são disparados em torno dos seus métodos @Test em uma ordem garantida. Compreender essa ordem — e a regra de que o JUnit cria uma nova instância da classe de teste para cada método de teste — é o que separa suítes de teste frágeis e dependentes de ordem das limpas e isoladas. Este capítulo percorre as cinco anotações de ciclo de vida e os dois ciclos de vida de instância que o JUnit oferece.
As cinco anotações de ciclo de vida
O JUnit 5 (o pacote org.junit.jupiter.api) define quatro anotações de callback que envolvem seus testes, além do próprio @Test. Para um tour mais completo de cada uma, veja anotações JUnit; se você é novo no framework, comece pela introdução ao JUnit.
| Anotação | Executa | O método deve ser |
|---|---|---|
@BeforeAll | Uma vez, antes de qualquer teste na classe | static (no ciclo de vida padrão) |
@BeforeEach | Antes de cada método @Test | instância |
@Test | O próprio teste | instância |
@AfterEach | Após cada método @Test | instância |
@AfterAll | Uma vez, após todos os testes terem sido executados | static (no ciclo de vida padrão) |
Uma única classe de teste com três testes dispara @BeforeAll uma vez, depois @BeforeEach → @Test → @AfterEach três vezes, e então @AfterAll uma vez.
import org.junit.jupiter.api.*;
class CalculatorTest {
@BeforeAll static void initSuite() { System.out.println("once, up front"); }
@BeforeEach void setUp() { System.out.println("before each test"); }
@Test void add() { Assertions.assertEquals(4, 2 + 2); }
@Test void subtract() { Assertions.assertEquals(0, 2 - 2); }
@AfterEach void tearDown() { System.out.println("after each test"); }
@AfterAll static void close() { System.out.println("once, at the end"); }
}Uma nova instância por método de teste
A regra mais importante do ciclo de vida: por padrão, o JUnit constrói uma nova instância da classe de teste antes de cada método de teste. Campos que você modifica em um teste não podem vazar para outro, porque o próximo teste é executado em um objeto diferente. É isso que torna os testes independentes da ordem de execução.
class IsolationTest {
private int counter = 0; // re-initialised for every test
@Test void first() { counter++; Assertions.assertEquals(1, counter); }
@Test void second() { counter++; Assertions.assertEquals(1, counter); } // also 1, not 2
}Ambos os testes veem counter == 1. Se o JUnit reutilizasse uma única instância, o segundo teste observaria 2 e passaria ou falharia dependendo da ordem — exatamente a fragilidade que este design previne.
PER_METHOD vs. PER_CLASS
Você pode sair do ciclo de vida por método usando @TestInstance(Lifecycle.PER_CLASS). Nesse caso, o JUnit cria uma instância para toda a classe, os campos de instância persistem entre os testes e — como conveniência — @BeforeAll/@AfterAll podem ser não-static.
| Aspecto | PER_METHOD (padrão) | PER_CLASS |
|---|---|---|
| Instâncias criadas | uma por @Test | uma por classe |
| Estado dos campos de instância | reiniciado a cada teste | compartilhado entre testes |
@BeforeAll/@AfterAll | deve ser static | pode ser método de instância |
| Melhor para | isolamento máximo | configuração compartilhada custosa |
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.TestInstance.Lifecycle;
@TestInstance(Lifecycle.PER_CLASS)
class SharedFixtureTest {
@BeforeAll void openConnection() { /* non-static is now legal */ }
@AfterAll void closeConnection() { }
}Use PER_CLASS apenas quando a configuração é genuinamente custosa e segura para compartilhar. O padrão oferece isolamento de graça.
Assertions são como um teste reporta falha
Um ciclo de vida existe para executar assertions. Assertions.assertEquals(expected, actual) lança um AssertionFailedError quando os valores diferem, o que interrompe esse único teste (seu @AfterEach ainda é executado) e o marca como falho — os outros testes continuam. Veja assertions JUnit para o conjunto completo de métodos assert*.
import static org.junit.jupiter.api.Assertions.*;
@Test void example() {
assertEquals(42, compute());
assertTrue(isReady());
assertThrows(IllegalArgumentException.class, () -> parse("bad"));
}Um exemplo prático: rastreando o ciclo de vida manualmente
Não há um executor JUnit neste playground de código, portanto o programa abaixo modela o ciclo de vida em código JDK simples: dispara os hooks na ordem do JUnit, contrasta PER_METHOD (uma nova instância por teste) com PER_CLASS (uma instância compartilhada), e termina com um pequeno harness de auto-verificação no espírito de assertEquals.
O que observar na execução:
- O bloco
PER_METHODimprimeinstance#1,instance#2,instance#3para os três testes, comprovando a regra padrão do JUnit: uma nova instância de teste é construída para cada método@Test, portanto nenhum teste pode ver o estado mutado de outro teste. - Em
PER_METHOD, cada linha[TEST]reportacounter=1, nunca2ou3. Cada instância obteve seu próprio campo novo, o que é por isso que os testes permanecem independentes da ordem de execução — o benefício central do ciclo de vida padrão. - O bloco
PER_CLASSreutilizainstance#1para todos os três testes, e seucountersobe1 → 2 → 3. Com uma única instância compartilhada, o estado dos campos de instância vaza deliberadamente entre os testes — útil para fixtures compartilhadas custosas, perigoso se você se esquecer disso. @BeforeAlle@AfterAllaparecem exatamente uma vez por bloco, envolvendo os pares@BeforeEach/@AfterEachpor teste que disparam três vezes — a ordem de aninhamento exata que o JUnit garante em torno de seus testes.- O harness final imprime
PASS:para todas as três verificações; umcheckque falha lança umAssertionErrorcom uma mensagemFAIL:, espelhando comoAssertions.assertEqualsinterrompe um único teste com umAssertionFailedErrorenquanto deixa os outros serem executados.
Capítulos relacionados
- Introdução ao JUnit — configure o JUnit 5 e execute seu primeiro teste.
- Anotações JUnit — cada anotação que molda o ciclo de vida.
- Assertions JUnit — como um teste de fato reporta aprovação ou falha.
- Testes parametrizados — execute um corpo de teste sobre muitas entradas.