Configurando o Kotest Runner no JUnit Platform (Maven)

Depois de concluirmos esta aula, nós:

  • Compreenderemos como compilar testes unitários escritos em Kotlin que utilizam o Kotest Framework 6.
  • Saberemos como executar especificações do Kotest sobre a JUnit Platform usando o Maven Surefire Plugin.
  • Saberemos como filtrar e executar apenas especificações que pertençam a uma Tag específica (UnitTest) e que sigam a convenção de nomenclatura *Spec.

[!NOTE] Onde entra o Kotest na stack moderna?

No curso original, o Pacote Master ensinava a escrever especificações BDD em Groovy utilizando o Spock Framework. No ecossistema moderno com Kotlin 2.4.21 e Spring Boot 4.1.2, não precisamos adicionar uma segunda linguagem dinâmica (Groovy) ao projeto: utilizamos o Kotest 6.2.6, o framework de especificações BDD, asserções e testes baseados em propriedades nativo para Kotlin!


1. Os Requisitos do Nosso Build Maven

Os requisitos do nosso build Maven são:

  • Nossa aplicação e nossos testes são escritos em Kotlin.
  • Ele deve suportar testes unitários escritos com o Kotest Framework 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.
  • Ele deve executar apenas os testes unitários que pertencem à tag UnitTest.

Vamos descobrir como declarar as dependências necessárias no arquivo pom.xml.


2. Obtendo as Dependências Necessárias

Para escrever e executar especificações com o Kotest 6 no JUnit Platform, precisamos declarar as seguintes dependências no nosso arquivo pom.xml:

  • Kotest Runner JUnit 5 / JUnit Platform (io.kotest:kotest-runner-junit5-jvm, versão 6.2.6): fornece a TestEngine (KotestJunitPlatformTestEngine) que permite ao JUnit Platform descobrir e executar especificações do Kotest nativamente.
  • Kotest Assertions Core (io.kotest:kotest-assertions-core-jvm, versão 6.2.6): fornece a biblioteca de matchers e asserções fluentes do Kotest (shouldBe, shouldThrow, assertSoftly, etc.).
  • Kotlin Stdlib (org.jetbrains.kotlin:kotlin-stdlib, versão 2.4.21): biblioteca padrão da linguagem Kotlin.

Depois de adicionarmos essas dependências ao nosso arquivo pom.xml, a seção dependencies fica assim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<dependencies>
    <dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-stdlib</artifactId>
        <version>2.4.21</version>
    </dependency>
    <dependency>
        <groupId>io.kotest</groupId>
        <artifactId>kotest-runner-junit5-jvm</artifactId>
        <version>6.2.6</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.kotest</groupId>
        <artifactId>kotest-assertions-core-jvm</artifactId>
        <version>6.2.6</version>
        <scope>test</scope>
    </dependency>
</dependencies>

[!TIP] Se você quiser importar todos os módulos do Kotest alinhados na mesma versão sem repetir <version>6.2.6</version>, também pode importar o BOM oficial io.kotest:kotest-bom:6.2.6 na seção <dependencyManagement> do seu pom.xml.

A seguir, vamos dar uma olhada rápida nas duas especificações do Kotest do nosso projeto de exemplo.


3. Conhecendo Nossas Especificações do Kotest

Nosso projeto de exemplo possui duas classes de especificação simples:

Primeiro, definimos um objeto UnitTest que estende io.kotest.core.Tag (ou usamos a anotação @Tags("UnitTest")) para identificar todas as especificações que devem rodar durante a fase de testes unitários:

1
2
3
4
5
package com.testwithspring.master

import io.kotest.core.Tag

object UnitTest : Tag()

A classe MessageServiceSpec especifica um teste unitário marcado com @Tags("UnitTest"):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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") {
            messageService.getMessage() shouldBe "Hello World!"
        }
    }
})

Segundo, a classe UnCategorizedSpec especifica um teste que não possui nenhuma tag associada. Em outras palavras, esta especificação não deve ser executada quando rodarmos nossa suíte filtrada por UnitTest:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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 when filtering by UnitTest tag") {
        messageService.getMessage() shouldBe "Hello World!"
    }
})

[!NOTE] Exploraremos a anatomia completa das especificações do Kotest e seus estilos (DescribeSpec, BehaviorSpec, ShouldSpec) nas próximas aulas deste tópico. Por ora, nosso foco é garantir que o build compile e filtre essas classes corretamente.

Vamos ver agora como compilar nossos testes unitários com o Maven.


4. Compilando Nossos Testes Unitários

Para compilar nosso código de produção (src/main/kotlin) e nossas especificações de teste (src/test/kotlin), configuramos o kotlin-maven-plugin na seção build do nosso pom.xml executando os goals compile e test-compile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<build>
    <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
    <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <version>2.4.21</version>
            <executions>
                <execution>
                    <id>compile</id>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
                <execution>
                    <id>test-compile</id>
                    <goals>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <jvmTarget>21</jvmTarget>
            </configuration>
        </plugin>
    </plugins>
</build>

Podemos compilar nossos testes unitários executando o seguinte comando no terminal:

1
mvn clean test-compile

Ao executar esse comando, veremos uma saída semelhante a esta:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$ mvn clean test-compile
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Running Unit Tests With Maven (Kotest Edition) 1.0.0
[INFO] ------------------------------------------------------------------------
[INFO] --- clean:3.4.0:clean (default-clean) @ running-unit-tests-with-maven ---
[INFO] --- resources:3.3.1:resources (default-resources) @ running-unit-tests-with-maven ---
[INFO] --- kotlin:2.4.21:compile (compile) @ running-unit-tests-with-maven ---
[INFO] Compiling 1 Kotlin source file to target/classes
[INFO] --- resources:3.3.1:testResources (default-testResources) @ running-unit-tests-with-maven ---
[INFO] --- kotlin:2.4.21:test-compile (test-compile) @ running-unit-tests-with-maven ---
[INFO] Compiling 3 Kotlin test source files to target/test-classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Essa saída confirma que o kotlin-maven-plugin compilou tanto a classe MessageService quanto nossas classes de especificação em src/test/kotlin.


5. Executando Nossos Testes Unitários com Maven Surefire

Para executar nossas especificações do Kotest, declaramos o Maven Surefire Plugin (maven-surefire-plugin) na seção plugins do pom.xml. Precisamos configurá-lo em dois passos:

Primeiro, garantimos que o Kotest execute apenas as especificações marcadas com a tag UnitTest. No Kotest 6, podemos passar a propriedade de sistema kotest.tags dentro do bloco <systemPropertyVariables> do Surefire.

Segundo, por padrão, versões clássicas do Maven Surefire Plugin procuram por classes cujo nome termine com Test, Tests ou TestCase. Quando escrevemos especificações com o Kotest (assim como no Spock), adotamos a convenção de terminar o nome das nossas classes com o sufixo Spec (ex: MessageServiceSpec). Por isso, configuramos o bloco <includes> para incluir **/*Spec.java (lembre-se de que o Surefire inspeciona o bytecode .class compilado mapeando padrões .java):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.5.2</version>
    <configuration>
        <includes>
            <include>**/*Spec.java</include>
            <include>**/*Test.java</include>
        </includes>
        <systemPropertyVariables>
            <kotest.tags>UnitTest</kotest.tags>
        </systemPropertyVariables>
    </configuration>
</plugin>

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

1
mvn clean test

Ao executar esse comando, vemos que o Maven Surefire Plugin descobre o KotestJunitPlatformTestEngine automaticamente, executa a especificação MessageServiceSpec e ignora UnCategorizedSpec:

1
2
3
4
5
6
7
8
9
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.testwithspring.master.MessageServiceSpec
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.085 s -- in com.testwithspring.master.MessageServiceSpec
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

Resumo

Nesta aula, aprendemos três pontos essenciais:

  • O artefato io.kotest:kotest-runner-junit5-jvm registra a engine do Kotest na JUnit Platform, permitindo sua execução transparente sem plugins proprietários obrigatórios.
  • Compilamos nossas especificações em src/test/kotlin usando o kotlin-maven-plugin.
  • Configuramos o maven-surefire-plugin para incluir classes terminadas com *Spec e filtrar a execução pela propriedade kotest.tags.