Arquitetura do JUnit 5
Objective
O JUnit 5 é dividido em três camadas para que engines de teste diferentes possam compartilhar um único launcher: a JUnit Platform descobre e executa testes através de uma API TestEngine, o JUnit Jupiter é o novo modelo de programação (anotações, asserções, extensões) mais a engine que o executa, e o JUnit Vintage é uma segunda engine que roda testes antigos de JUnit 3/4 sem modificação na mesma Platform, o que é o que torna a migração do JUnit 4 incremental em vez de uma reescrita tudo-ou-nada.
Use Cases
- Rodar testes JUnit 5 (Jupiter) e testes legados de JUnit 4 (Vintage) no mesmo build Maven/Gradle, reportados em um único conjunto de resultados unificado.
- Migrar uma suíte de testes de JUnit 4 para JUnit 5 módulo por módulo, mantendo os módulos não tocados no Vintage enquanto os convertidos migram para o Jupiter.
- Entender, quando uma IDE ou plugin Maven roda "testes JUnit", qual engine de fato executou uma determinada classe de teste.
- Registrar uma integração de terceiros (Mockito, Spring) via
@ExtendWithem vez de um@RunWithde valor único no estilo JUnit 4.
Deep Dive
A arquitetura de três camadas
plaintextJUnit Platform (descoberta + API de launcher, usada por IDEs e ferramentas de build) ├── JUnit Jupiter Engine → roda métodos @Test escritos com a API do JUnit 5 └── JUnit Vintage Engine → roda métodos @Test antigos de JUnit 3/4 sem alteração
- A JUnit Platform define a interface de provedor de serviço
TestEnginee a APILauncher. Ela não sabe o que é uma anotação@Test, apenas pergunta a cada engine registrada "quais dessas classes você consegue rodar, e o que aconteceu quando você rodou?". - O JUnit Jupiter é o que a maioria das pessoas entende por "JUnit 5": as anotações
org.junit.jupiter.api(@Test,@BeforeEach, ...), as classesAssertions/Assumptions, e o modelo de extensão. A engine Jupiter implementaTestEnginepara executar tudo isso sobre a Platform. - O JUnit Vintage implementa
TestEnginepara o modelo antigoorg.junit.Test, de modo que um projeto pode adicionar as duas engines como dependência e obter uma única execução de testes cobrindo os antigos e os novos juntos.
plaintext<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <scope>test</scope> </dependency>
Migrando do JUnit 4: uma nota rápida
O livro dedica um capítulo inteiro (Cap. 4) a migrar suítes de teste JUnit 4 para JUnit 5, com mapeamento anotação por anotação (
@Before/@Afterpara@BeforeEach/@AfterEach,@Categorypara@Tag,@RunWithpara@ExtendWith), e substituindo campos@Rule/@ClassRule(TemporaryFolder,ExpectedException) pelo modeloExtension. Poucos projetos novos começam em JUnit 4 hoje, então este conceito mantém esse mapeamento como uma tabela de referência rápida em vez de um conceito próprio:
| JUnit 4 | JUnit 5 (Jupiter) |
|---|---|
org.junit.Test (precisa ser public) |
org.junit.jupiter.api.Test (package-private já basta) |
@Before / @After |
@BeforeEach / @AfterEach |
@BeforeClass / @AfterClass |
@BeforeAll / @AfterAll |
@Ignore |
@Disabled |
@Category(SlowTests.class) (interface marcadora) |
@Tag("slow") (string simples) |
@RunWith(SomeRunner.class) (valor único) |
@ExtendWith(SomeExtension.class) (repetível) |
@Rule public ExpectedException thrown |
assertThrows(...) ou uma Extension customizada |
Um @RunWith(MockitoJUnitRunner.class) do JUnit 4 vira @ExtendWith(MockitoExtension.class), mesmo propósito (criar campos @Mock antes do teste rodar), mecanismo de registro diferente, e agora empilhável com outras extensões na mesma classe.
Trade-offs
- O Vintage mantém testes antigos rodando, mas não os moderniza: rodar testes JUnit 4 sem alteração pela engine Vintage evita uma reescrita, mas o time ainda carrega dois idiomas (
@Rule/runners de JUnit 4 ao lado de extensões de JUnit 5) até cada teste ser migrado. @RunWithé de valor único,@ExtendWithé repetível: o JUnit 4 permitia apenas um runner por classe, forçando composições desajeitadas de runner; o JUnit 5 permite que uma classe combine múltiplas extensões independentes:
java@ExtendWith(MockitoExtension.class)
@ExtendWith(SpringExtension.class) // both apply, no conflict
class MultiExtensionTest { /* ... */ }- Classes/métodos de teste package-private funcionam no Jupiter, não no Vintage: uma classe escrita para a engine nova pode dispensar
public, mas essa mesma classe, se rodada pela engine Vintage (como se fosse JUnit 4 legado), ainda precisa depublicpara ser descoberta.