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 @Category do JUnit 4).
  • Seremos capazes de configurar a task test do Gradle usando useJUnitPlatform { 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:

1
2
3
4
5
6
7
plugins {
    kotlin("jvm") version "2.4.21"
}

tasks.withType<Test>().configureEach {
    useJUnitPlatform()
}

[!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 chamada useJUnitPlatform() na task Test é 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ório src/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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package com.testwithspring.starter.unittests

import org.junit.jupiter.api.Test

class DefaultTest {

    @Test
    fun defaultTest() {
        println("The default configuration")
    }
}

Agora podemos executar nossos testes unitários rodando o seguinte comando no terminal:

1
./gradlew clean test

[!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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
> Task :clean
> Task :compileKotlin
> Task :compileJava NO-SOURCE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :compileTestKotlin
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test

com.testwithspring.starter.unittests.DefaultTest > defaultTest() STANDARD_OUT
    The default configuration

BUILD SUCCESSFUL in 1s

[!TIP] Por que o Gradle não imprime o println / System.out por padrão? Por padrão, o Gradle oculta no console o que é escrito em System.out ou System.err durante testes que passam. Podemos habilitar essa impressão facilmente adicionando testLogging { showStandardStreams = true } dentro do bloco tasks.withType<Test>() no nosso build.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 {}):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.testwithspring.starter.unittests

import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test

@Tag("unitTest")
class CategoryTest {

    @Test
    fun categoryTest() {
        println("The category: 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:

  1. Habilitamos o suporte ao JUnit Platform invocando useJUnitPlatform { ... } (no JUnit 4 antigo usava-se useJUnit { includeCategories '...' }).
  2. Passamos o nome da tag ("unitTest") para o método includeTags("unitTest") dentro do bloco useJUnitPlatform.

Adicionamos as seguintes linhas ao nosso arquivo build.gradle.kts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
tasks.named<Test>("test") {
    useJUnitPlatform {
        // No JUnit 6 usamos includeTags("unitTest")
        // (No JUnit 4 com Groovy DSL era: useJUnit { includeCategories 'com.testwithspring.starter.unittests.UnitTest' })
        includeTags("unitTest")
    }
    testLogging {
        showStandardStreams = true
    }
}

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:

1
2
3
4
5
6
7
8
9
> Task :clean
> Task :compileKotlin
> Task :compileTestKotlin
> Task :test

com.testwithspring.starter.unittests.CategoryTest > categoryTest() STANDARD_OUT
    The category: UnitTest

BUILD SUCCESSFUL in 1s

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") (ou java) e habilitando useJUnitPlatform().
  • O Gradle consegue diferenciar testes unitários, de integração e End-to-End tanto via Test Suites / SourceSets quanto via Tags.
  • Usar @Tag do JUnit 6 (o sucessor de @Category do 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 bloco useJUnitPlatform (que substituiu o antigo includeCategories do JUnit 4).