Executando Testes Unitários com Gradle 9 e JUnit 6
Ao concluirmos esta aula, nós:
- Saberemos como configurar e executar testes unitários com o Gradle 9 (Kotlin DSL).
- Compreenderemos por que devemos categorizar nossos testes utilizando Tags do JUnit 6 (
@Tag) (substituindo as antigas@Categorydo JUnit 4). - Seremos capazes de configurar a task
testdo Gradle usandouseJUnitPlatform { includeTags(...) }para rodar apenas os testes unitários da categoria selecionada.
Executando Testes Unitários com o Gradle
Quando criamos um projeto Kotlin/JVM ou Java com o Gradle 9, aplicamos o plugin kotlin("jvm") (ou o plugin java) e configuramos a task de testes para utilizar a plataforma do JUnit 6 (useJUnitPlatform()). Fazemos isso adicionando o seguinte trecho ao nosso arquivo build.gradle.kts:
|
|
[!NOTE] No Gradle legado (Groovy DSL com JUnit 4), aplicava-se
apply plugin: 'java'e o JUnit 4 era o runner padrão. A partir do JUnit 5 e no JUnit 6, a chamadauseJUnitPlatform()na taskTesté obrigatória para ativar o motor JUnit Platform / Jupiter!
Quando utilizamos a configuração padrão, o Gradle executa todos os testes encontrados nas classes compiladas a partir do diretório src/test/kotlin (e src/test/java). Como estamos utilizando o JUnit Jupiter 6, o motor de descoberta identifica automaticamente qualquer classe que contenha pelo menos um método anotado com @Test, @ParameterizedTest, @RepeatedTest ou @TestFactory (ou classes internas anotadas com @Nested).
[!TIP] Se os nossos testes unitários possuírem recursos estáticos — como arquivos de propriedades, JSONs de payload ou arquivos de configuração de log (
logback-test.xml) —, devemos colocá-los no diretóriosrc/test/resources.
Vamos escrever um teste unitário simples que escreve a string "The default configuration" no System.out (println). Após escrevermos esse teste, o código-fonte da nossa classe DefaultTest fica assim:
|
|
Agora podemos executar nossos testes unitários rodando o seguinte comando no terminal:
|
|
[!NOTE] Se não quisermos limpar (
clean) os artefatos compilados antes de executar nossos testes unitários (aproveitando o cache incremental do Gradle 9!), basta usar o comando:
1./gradlew test
Quando executamos nossos testes unitários (com a exibição de standard output habilitada no testLogging), vemos que o Gradle executa nosso teste:
Initialized native services in: /home/wc/.gradle/native
Initialized jansi services in: /home/wc/.gradle/native
The client will now receive all logging from the daemon (pid: 18240, address: [fe80::1%1:54340]).
Gradle Daemon started in 1 s 012 ms
Starting Build
Settings evaluated using settings file '/home/wc/test-with-spring/code/01-starter-junit6/settings.gradle.kts'.
Projects loaded. Root project using build file '/home/wc/test-with-spring/code/01-starter-junit6/build.gradle.kts'.
Included projects: [root project 'starter-junit6']
> Configure project :
Evaluating root project 'starter-junit6' using build file '/home/wc/test-with-spring/code/01-starter-junit6/build.gradle.kts'.
Using Kotlin Gradle Plugin 2.4.21
Using Java Toolchain: Temurin 21.0.6 (/home/wc/.sdkman/candidates/java/21.0.6-tem)
Spring Boot 4.1.2 plugin applied.
All projects evaluated.
Selected primary task 'test' from project :
Tasks to be executed: [task ':compileKotlin', task ':compileJava', task ':processResources', task ':classes', task ':compileTestKotlin', task ':compileTestJava', task ':processTestResources', task ':testClasses', task ':test']
Tasks that were skipped: [task ':compileJava', task ':compileTestJava']
> Task :compileKotlin UP-TO-DATE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :compileTestKotlin
Compiling with Kotlin compiler version 2.4.21 (JVM target 21)
Compiling Kotlin files from [/home/wc/test-with-spring/code/01-starter-junit6/src/test/kotlin]
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
Starting process 'Gradle Test Executor 1'. Working directory: /home/wc/test-with-spring/code/01-starter-junit6 Command: /home/wc/.sdkman/candidates/java/21.0.6-tem/bin/java -ea -cp ...
Successfully started process 'Gradle Test Executor 1'
com.testwithspring.starter.unittests.DefaultTest > defaultTest() STANDARD_OUT
The default configuration
com.testwithspring.starter.unittests.DefaultTest > defaultTest() PASSED
DefaultTest > 1 test completed, 0 failed, 0 skipped
Finished generating test XML results (0.009 secs) into: /home/wc/test-with-spring/code/01-starter-junit6/build/test-results/test
Generating HTML test report...
Finished generating test html results (0.015 secs) into: /home/wc/test-with-spring/code/01-starter-junit6/build/reports/tests/test
BUILD SUCCESSFUL in 1s
5 actionable tasks: 2 executed, 3 up-to-date
[!TIP] Por que o Gradle não imprime o
println/System.outpor padrão? Por padrão, o Gradle oculta no console o que é escrito emSystem.outouSystem.errdurante testes que passam. Podemos habilitar essa impressão facilmente adicionandotestLogging { showStandardStreams = true }dentro do blocotasks.withType<Test>()no nossobuild.gradle.kts!
Se escrevermos apenas testes unitários, não precisamos fazer mais alterações no script de build. Porém, na maioria dos projetos, também escreveremos testes de integração e testes End-to-End. Vamos descobrir como usar categorias/tags do JUnit com o Gradle!
Configurando a Categoria (@Tag) dos Nossos Testes Unitários no Gradle
Embora o Gradle permita separar testes unitários, testes de integração e testes End-to-End utilizando conjuntos de código-fonte distintos (SourceSets / JVM Test Suites, como veremos no Capítulo 02), usar Tags do JUnit 6 (@Tag) (que substituíram as @Category do JUnit 4) com o Gradle ainda é extremamente útil!
Por quê? Porque as Tags do JUnit nos dão a possibilidade de criar subcategorias dentro de uma mesma suíte e marcar classes ou métodos individuais — por exemplo, separando testes rápidos que rodam localmente na máquina do desenvolvedor de testes lentos que devem rodar apenas no servidor de CI.
Vamos começar criando a classe CategoryTest que contém um método de teste que escreve "The category: UnitTest" no System.out e é anotada com @Tag("unitTest") (no JUnit 4 usávamos @Category(UnitTest::class) com uma interface marcadora interface UnitTest {}):
|
|
Depois de criarmos nossa classe de teste e configurarmos sua categoria/tag, precisamos instruir o Gradle a executar na task test apenas os testes que pertencem à tag unitTest. Fazemos isso na configuração da task test seguindo estes dois passos:
- Habilitamos o suporte ao JUnit Platform invocando
useJUnitPlatform { ... }(no JUnit 4 antigo usava-seuseJUnit { includeCategories '...' }). - Passamos o nome da tag (
"unitTest") para o métodoincludeTags("unitTest")dentro do blocouseJUnitPlatform.
Adicionamos as seguintes linhas ao nosso arquivo build.gradle.kts:
|
|
Quando executamos ./gradlew clean test agora, observamos que o Gradle executa apenas o teste encontrado na classe CategoryTest e ignora o teste da classe DefaultTest:
Initialized native services in: /home/wc/.gradle/native
Initialized jansi services in: /home/wc/.gradle/native
The client will now receive all logging from the daemon (pid: 18240, address: [fe80::1%1:54340]).
Starting Build
Settings evaluated using settings file '/home/wc/test-with-spring/code/01-starter-junit6/settings.gradle.kts'.
Projects loaded. Root project using build file '/home/wc/test-with-spring/code/01-starter-junit6/build.gradle.kts'.
Included projects: [root project 'starter-junit6']
> Configure project :
Evaluating root project 'starter-junit6' using build file '/home/wc/test-with-spring/code/01-starter-junit6/build.gradle.kts'.
Using Kotlin Gradle Plugin 2.4.21
Using Java Toolchain: Temurin 21.0.6 (/home/wc/.sdkman/candidates/java/21.0.6-tem)
Spring Boot 4.1.2 plugin applied.
All projects evaluated.
Selected primary task 'test' from project :
Tasks to be executed: [task ':compileKotlin', task ':compileJava', task ':processResources', task ':classes', task ':compileTestKotlin', task ':compileTestJava', task ':processTestResources', task ':testClasses', task ':test']
Tasks that were skipped: [task ':compileJava', task ':compileTestJava']
> Task :compileKotlin UP-TO-DATE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :compileTestKotlin UP-TO-DATE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test
Starting process 'Gradle Test Executor 1'. Working directory: /home/wc/test-with-spring/code/01-starter-junit6 Command: /home/wc/.sdkman/candidates/java/21.0.6-tem/bin/java -ea -cp ...
Successfully started process 'Gradle Test Executor 1'
com.testwithspring.starter.unittests.CategoryTest > categoryTest() STANDARD_OUT
The category: UnitTest
com.testwithspring.starter.unittests.CategoryTest > categoryTest() PASSED
CategoryTest > 1 test completed, 0 failed, 0 skipped
Finished generating test XML results (0.008 secs) into: /home/wc/test-with-spring/code/01-starter-junit6/build/test-results/test
Generating HTML test report...
Finished generating test html results (0.014 secs) into: /home/wc/test-with-spring/code/01-starter-junit6/build/reports/tests/test
BUILD SUCCESSFUL in 980ms
5 actionable tasks: 1 executed, 4 up-to-date
Vamos resumir o que aprendemos nesta aula!
Resumo
Nesta aula, aprendemos quatro pontos essenciais:
- Executamos nossos testes unitários no Gradle aplicando o plugin
kotlin("jvm")(oujava) e habilitandouseJUnitPlatform(). - O Gradle consegue diferenciar testes unitários, de integração e End-to-End tanto via Test Suites / SourceSets quanto via Tags.
- Usar
@Tagdo JUnit 6 (o sucessor de@Categorydo JUnit 4) oferece configuração flexível em nível de classe ou método, permitindo separar testes rápidos locais de testes lentos de CI. - Especificamos a tag/categoria utilizada no Gradle 9 através da opção
includeTags("unitTest")dentro do blocouseJUnitPlatform(que substituiu o antigoincludeCategoriesdo JUnit 4).