Introdução à Arquitetura e Ciclo de Vida do JUnit Jupiter (JUnit 5 / 6)
Depois de concluirmos esta aula bônus, nós:
- Saberemos declarar as dependências necessárias da plataforma JUnit (API, Engine, Vintage e Platform Launcher) no Maven e no Gradle.
- Saberemos executar nossos testes unitários no Maven e no Gradle exibindo a saída padrão no console (
showStandardStreams). - Estaremos familiarizados com as anotações fundamentais do JUnit Jupiter (
@DisplayName,@Test,@BeforeAll,@BeforeEach,@AfterEache@AfterAll). - Compreenderemos a ordem exata em que os métodos de setup, teste e teardown são executados.
1. Configurando um Projeto Maven
Podemos configurar um build Maven para executar nossos testes unitários no JUnit Jupiter seguindo estes dois passos:
Passo 1: Declarar as Dependências no pom.xml
Primeiro, declaramos a dependência junit-jupiter-api no nosso arquivo pom.xml. Essa dependência fornece a API pública para escrevermos testes e extensões (@Test, @BeforeEach, @DisplayName, Assertions, etc.).
Além disso, para que o runtime de execução e as IDEs executem nossos testes (incluindo suporte opcional a testes legados do JUnit 4 durante uma migração), conhecemos os módulos que compõem a arquitetura do JUnit:
junit-jupiter-api: Contém a API pública para escrever testes e extensões no modelo JUnit Jupiter.junit-jupiter-engine: Contém a implementação do motor de execução (TestEngine) do JUnit Jupiter (necessário apenas em tempo de execução —runtime).junit-vintage-engine: Adiciona suporte para executar testes legados escritos em JUnit 3 ou JUnit 4 na nova plataforma.junit-platform-launcher: Fornece a API pública para configurar e iniciar a execução de testes (utilizada por IDEs e ferramentas de build como Gradle e Maven).junit-platform-runner: Permite executar testes e suítes da JUnit Platform dentro de um ambiente legado JUnit 4.
Veja como essas dependências são declaradas na seção <dependencies> do pom.xml:
|
|
Passo 2: Configurar o maven-surefire-plugin
Segundo, declaramos o plugin maven-surefire-plugin na seção <plugins> do nosso pom.xml para que o comando mvn test descubra e execute todos os testes na JUnit Platform:
|
|
(Nota histórica: nas primeiras versões milestone do JUnit 5 com Surefire 2.19.1, era necessário declarar a dependência junit-platform-surefire-provider dentro do <plugin>; a partir do Surefire 2.22.0+, o suporte à JUnit Platform já é 100% nativo!).
Vamos ver agora como configurar um projeto Gradle para executar nossos testes unitários.
2. Configurando um Projeto Gradle
No Gradle moderno (Gradle 9 com Kotlin DSL), o suporte à JUnit Platform é nativo na task Test via useJUnitPlatform() (substituindo o antigo plugin org.junit.platform.gradle.plugin usado nas versões iniciais do JUnit 5).
Podemos configurar nosso projeto Gradle seguindo estes passos:
- Adicionar a dependência
org.junit.jupiter:junit-jupiter-apina configuraçãotestImplementation(API de compilação dos testes). - Adicionar o motor de execução
org.junit.jupiter:junit-jupiter-enginee oorg.junit.platform:junit-platform-launcherna configuraçãotestRuntimeOnly(necessários apenas em tempo de execução). - Habilitar
useJUnitPlatform()na tasktest. - Configurar
testLogging { showStandardStreams = true }dentro da tasktest. Isso faz com que o Gradle imprima no console tudo o que for escrito emSystem.out(println) ouSystem.errdurante a execução dos nossos testes — o que nos permitirá visualizar na prática a ordem exata de invocação dos métodos de ciclo de vida!
Depois de seguirmos esses passos, nosso arquivo build.gradle.kts fica assim:
|
|
Vamos avançar e escrever a nossa primeira classe de teste demonstrando todo o ciclo de vida do JUnit Jupiter.
3. Escrevendo Nossa Primeira Classe de Teste
Uma classe de teste no JUnit Jupiter é apenas uma classe Kotlin ou Java comum (no Java, ela nem precisa ser public — pode ser package-private). Podemos criar nossa primeira classe de teste em dois passos:
- Criar a classe
JUnit5ExampleTest. - Anotar a classe com
@DisplayName("JUnit 5 Example"). Esse passo é opcional, mas altamente recomendado porque a anotação@DisplayNamenos permite usar frases reais e legíveis em vez de apenas nomes técnicos de classes nos relatórios da IDE e do Gradle!
|
|
Nosso próximo passo é adicionar os métodos de configuração (setup) e limpeza (teardown) à nossa classe de teste.
3.1. Utilizando Métodos de Setup e Teardown
Os métodos de setup e teardown são métodos que não retornam valor (Unit / void) e são invocados antes ou depois dos métodos de teste (anotados com @Test, @RepeatedTest, @ParameterizedTest ou @TestFactory).
Vamos adicionar todos os quatro métodos de ciclo de vida à nossa classe de teste seguindo estes passos:
@BeforeAll: Adicionamos um método estáticobeforeAll()anotado com@BeforeAll(dentro decompanion objectcom@JvmStaticem Kotlin). Esse método é invocado uma única vez antes que qualquer método de teste da classe seja executado, e é herdado por subclasses.@BeforeEach: Adicionamos um métodobeforeEach()anotado com@BeforeEach. Esse método é invocado antes de cada método de teste, e também é herdado por subclasses.@AfterEach: Adicionamos um métodoafterEach()anotado com@AfterEach. Esse método é invocado após cada método de teste, e também é herdado.@AfterAll: Adicionamos um método estáticoafterAll()anotado com@AfterAll. Esse método é invocado uma única vez depois que todos os métodos de teste da classe terminaram de executar, e também é herdado.
Depois de adicionarmos todos os métodos de setup e teardown à nossa classe de teste, seu código-fonte fica assim:
|
|
Dica: Se você anotar sua classe de teste com
@TestInstance(TestInstance.Lifecycle.PER_CLASS), os métodos anotados com@BeforeAlle@AfterAllnão precisam ser estáticos (companion object/@JvmStatic), podendo ser métodos normais da classe!
3.2. Escrevendo Nossos Primeiros Métodos de Teste (@Test)
Agora vamos adicionar dois métodos de teste anotados com @Test e @DisplayName à nossa classe:
- O método
firstTest()possui o display name"First test"e imprime"First test method"no console. - O método
secondTest()possui o display name"Second test"e imprime"Second test method"no console.
Veja o código completo da classe JUnit5ExampleTest:
|
|
4. Executando Nossos Testes Unitários
Quando executamos nossos testes unitários (./gradlew test), vemos a seguinte saída no console:
|
|
Essa saída comprova visualmente que os métodos de setup (@BeforeAll, @BeforeEach), os métodos de teste (@Test) e os métodos de teardown (@AfterEach, @AfterAll) são executados exatamente na ordem esperada!
Resumo
Esta aula bônus nos ensinou quatro pontos essenciais:
- A dependência
junit-jupiter-apifornece as anotações e asserções para escrevermos testes e extensões. - Devemos garantir que o motor de execução (
junit-jupiter-engine) esteja presente no classpath de runtime (testRuntimeOnlyno Gradle oumaven-surefire-pluginno Maven) ao executarmos nossos testes. - O JUnit Jupiter suporta classes e métodos de teste não públicos (
internalem Kotlin / package-private em Java) e fornece o ciclo de vida completo com@BeforeAll,@BeforeEach,@AfterEache@AfterAll. - Podemos sobrescrever o nome exibido de qualquer classe ou método de teste utilizando a anotação
@DisplayName(ou nomes de função entre crases no Kotlin).