Executando Especificações Kotest com Gradle 9

Depois de concluirmos esta aula, nós:

  • Compreenderemos como compilar especificações de teste do Kotest 6 usando o plugin Kotlin JVM no Gradle 9.
  • Saberemos como configurar a tarefa test (ou jvm-test-suite) para executar especificações do Kotest sobre a JUnit Platform.
  • Saberemos como filtrar a execução para rodar apenas especificações que pertençam à tag UnitTest.

1. Os Requisitos do Nosso Build Gradle

Os requisitos do nosso build Gradle 9.8.2 são:

  • Nossa aplicação e nossos testes são escritos em Kotlin 2.4.21.
  • O build deve suportar especificações escritas com o Kotest Framework 6.2.6.
  • O diretório de código-fonte dos nossos testes unitários deve ser src/test/kotlin.
  • O diretório de recursos dos nossos testes unitários deve ser src/test/resources.
  • A tarefa test deve executar apenas as especificações marcadas com a tag UnitTest.

Vamos começar declarando as dependências necessárias no arquivo build.gradle.kts.


2. Obtendo as Dependências Necessárias

Para escrever e executar especificações do Kotest 6 com Gradle, adicionamos as seguintes dependências no bloco dependencies do nosso build.gradle.kts:

  • Kotest BOM (io.kotest:kotest-bom:6.2.6): alinha as versões de todos os módulos do Kotest.
  • Kotest Runner JUnit 5 (io.kotest:kotest-runner-junit5): integra o Kotest à JUnit Platform usada pelo Gradle.
  • Kotest Assertions Core (io.kotest:kotest-assertions-core): fornece os matchers fluentes (shouldBe, shouldContain, etc.).
1
2
3
4
5
dependencies {
    testImplementation(platform("io.kotest:kotest-bom:6.2.6"))
    testImplementation("io.kotest:kotest-runner-junit5")
    testImplementation("io.kotest:kotest-assertions-core")
}

A seguir, vamos revisar nossas duas classes de especificação de exemplo.


3. Conhecendo Nossas Especificações do Kotest

Nosso projeto de exemplo contém duas classes de especificação em src/test/kotlin/com/testwithspring/master:

Primeiro, a classe MessageServiceSpec possui um teste unitário marcado com @Tags("UnitTest") e imprime uma mensagem no console (println) para visualizarmos claramente sua execução no log do Gradle:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.testwithspring.master

import io.kotest.core.annotation.Tags
import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe

@Tags("UnitTest")
class MessageServiceSpec : ShouldSpec({

    val messageService = MessageService()

    context("Get message") {
        should("return the correct message") {
            println("Should return the correct message")
            messageService.getMessage() shouldBe "Hello World!"
        }
    }
})

A tag UnitTest pode ser referenciada diretamente como String em @Tags("UnitTest") ou declarada como um singleton que herda de io.kotest.core.Tag:

1
2
3
4
5
package com.testwithspring.master

import io.kotest.core.Tag

object UnitTest : Tag()

Segundo, a classe UnCategorizedSpec especifica um teste que não possui nenhuma tag. Portanto, ele não deve ser executado quando filtrarmos nossos testes unitários pela tag UnitTest:

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

import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe

class UnCategorizedSpec : ShouldSpec({

    val messageService = MessageService()

    should("not be run") {
        println("Should not be run")
        messageService.getMessage() shouldBe "Hello World!"
    }
})

[!NOTE] Assim como vimos no Capítulo 01, usamos println neste exemplo exclusivamente para comprovar visualmente no terminal qual especificação foi executada pelo Gradle. Em projetos reais, não precisamos poluir os testes com println.

Vamos ver como o Gradle compila nossas especificações.


4. Compilando Nossos Testes Unitários com o Plugin Kotlin

Como tanto nossa aplicação quanto nossas especificações do Kotest são escritas em Kotlin, basta aplicarmos o plugin kotlin("jvm") no build.gradle.kts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
plugins {
    kotlin("jvm") version "2.4.21"
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.slf4j:slf4j-api:2.0.17")
    runtimeOnly("ch.qos.logback:logback-classic:1.5.18")

    testImplementation(platform("io.kotest:kotest-bom:6.2.6"))
    testImplementation("io.kotest:kotest-runner-junit5")
    testImplementation("io.kotest:kotest-assertions-core")
}

Como o plugin kotlin("jvm") estende o plugin java do Gradle, todos os diretórios padrão já estão configurados automaticamente:

  • src/main/kotlin: contém o código-fonte da nossa aplicação.
  • src/main/resources: contém os recursos da nossa aplicação.
  • src/test/kotlin: contém o código-fonte das nossas especificações de teste unitário.
  • src/test/resources: contém os recursos dos nossos testes unitários.

Além disso, diferentemente do Maven Surefire legado, o Gradle inspeciona as classes de teste por meio da JUnit Platform Discovery API, descobrindo qualquer classe que herde de Spec do Kotest independentemente de o nome terminar com Test ou Spec!


5. Configurando a Tag dos Nossos Testes Unitários

Para garantir que a tarefa test do Gradle execute as especificações do Kotest e filtre apenas aquelas pertencentes à tag UnitTest, fazemos três configurações no bloco tasks.withType<Test>():

  1. Habilitamos o suporte à JUnit Platform chamando useJUnitPlatform().
  2. Configuramos a propriedade de sistema kotest.tags com o valor "UnitTest" (você também pode usar expressões booleanas como "UnitTest & !Slow").
  3. Habilitamos testLogging.showStandardStreams = true para visualizarmos a saída escrita em System.out no terminal.

Veja como fica o nosso build.gradle.kts completo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
plugins {
    kotlin("jvm") version "2.4.21"
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.slf4j:slf4j-api:2.0.17")
    runtimeOnly("ch.qos.logback:logback-classic:1.5.18")

    testImplementation(platform("io.kotest:kotest-bom:6.2.6"))
    testImplementation("io.kotest:kotest-runner-junit5")
    testImplementation("io.kotest:kotest-assertions-core")
}

tasks.withType<Test>().configureEach {
    useJUnitPlatform()
    systemProperty("kotest.tags", "UnitTest")
    testLogging {
        showStandardStreams = true
    }
}

6. Executando Nossos Testes Unitários com Gradle

Agora podemos executar nossas especificações rodando o seguinte comando no terminal:

1
./gradlew clean test

Ao executar esse comando, notamos que o Gradle roda apenas a especificação MessageServiceSpec e ignora UnCategorizedSpec:

1
2
3
4
5
6
7
8
9
$ ./gradlew clean test

> Task :test

com.testwithspring.master.MessageServiceSpec > Get message > return the correct message STANDARD_OUT
    Should return the correct message

BUILD SUCCESSFUL in 2s
4 actionable tasks: 4 executed

[!TIP] Você também pode sobrescrever as tags diretamente via linha de comando sem alterar o build.gradle.kts passando -Dkotest.tags="UnitTest":

1
./gradlew test -Dkotest.tags="UnitTest"

Resumo

Nesta aula, aprendemos três pontos fundamentais:

  • O plugin kotlin("jvm") compila nossas especificações em src/test/kotlin sem exigir diretórios extras ou compiladores secundários.
  • O Kotest 6 roda sobre a JUnit Platform quando ativamos useJUnitPlatform() na tarefa Test do Gradle.
  • Podemos filtrar quais especificações são executadas configurando systemProperty("kotest.tags", "UnitTest").