Configurando Compilador Kotlin 2.4 e Plugins Spring

Ao final desta aula, seremos capazes de configurar um projeto moderno que compila e executa tanto testes unitários quanto testes de integração escritos em Kotlin 2.4.21 com JUnit Jupiter 6.0.3, AssertJ 3.27.7 e suporte aos plugins do Spring Boot 4.1.2 (tanto em builds Maven quanto a equivalência direta no Gradle 9.8.2 Kotlin DSL).

Os Requisitos do Nosso Build

Os requisitos para a configuração do nosso build são:

  • Todo o código (aplicação e testes) deve utilizar Kotlin 2.4.21.
  • Testes unitários e testes de integração devem possuir diretórios de código-fonte e de recursos (resources) separados.
  • Deve ser possível executar apenas os testes unitários ou apenas os testes de integração.
  • Deve ser possível executar ambos os testes (unitários e de integração) em uma única execução.
  • Apenas os testes unitários devem ser executados por padrão.

Vamos começar dando uma rápida olhada no sistema sob teste (System Under Test - SUT).

Introdução ao Sistema Sob Teste (SUT)

Durante esta aula, escreveremos tanto testes unitários quanto testes de integração para uma classe MessageService. Esta classe possui um único método chamado getMessage(), que retorna a mensagem: 'Hello World!'.

O código-fonte da classe MessageService (anotada com @Service no ecossistema Spring Boot 4.1.2) é o seguinte:

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

import org.springframework.stereotype.Service

@Service
class MessageService {

    fun getMessage(): String {
        return "Hello World!"
    }
}

A seguir, veremos como criar e configurar um projeto Kotlin simples capaz de compilar o código (incluindo o plugin spring / all-open do compilador Kotlin 2.4) e executar inicialmente apenas testes unitários.

Executando Testes Unitários no Build

Quando queremos configurar um projeto Kotlin que executa apenas testes unitários, precisamos seguir cinco passos fundamentais:

Primeiro, precisamos especificar as propriedades de versão e compilação:

  • A propriedade kotlin.version especifica a versão utilizada do Kotlin (2.4.21).
  • A propriedade kotlin.compiler.incremental habilita a compilação incremental no compilador Kotlin.
  • A propriedade kotlin.compiler.jvmTarget define o bytecode alvo da JVM (21).
  • A propriedade junit-jupiter.version especifica a versão utilizada do JUnit 6 (6.0.3).
  • A propriedade assertj.version especifica a versão do AssertJ (3.27.7).
  • A propriedade mockk.version especifica a versão do MockK (1.14.0).

Após especificarmos essas propriedades, a seção properties do nosso arquivo pom.xml (e o catálogo correspondente no build.gradle.kts do Gradle 9.8.2) fica da seguinte forma:

1
2
3
4
5
6
7
8
9
<properties>
    <kotlin.version>2.4.21</kotlin.version>
    <kotlin.compiler.incremental>true</kotlin.compiler.incremental>
    <kotlin.compiler.jvmTarget>21</kotlin.compiler.jvmTarget>
    <spring-boot.version>4.1.2</spring-boot.version>
    <junit-jupiter.version>6.0.3</junit-jupiter.version>
    <assertj.version>3.27.7</assertj.version>
    <mockk.version>1.14.0</mockk.version>
</properties>

No Gradle 9.8.2 Kotlin DSL (build.gradle.kts), declaramos os plugins do Kotlin 2.4.21 e do Spring Boot 4.1.2, incluindo o plugin plugin.spring (kotlin-spring / all-open) para que classes anotadas com @Configuration, @Service, @Component ou @Transactional sejam abertas automaticamente para instrumentação de proxies do Spring:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
plugins {
    kotlin("jvm") version "2.4.21"
    kotlin("plugin.spring") version "2.4.21"
    id("org.springframework.boot") version "4.1.2"
    id("io.spring.dependency-management") version "1.1.7"
}

kotlin {
    jvmToolchain(21)
    compilerOptions {
        freeCompilerArgs.addAll("-Xjsr305=strict")
    }
}

Segundo, precisamos especificar as dependências necessárias. Como queremos escrever testes para uma aplicação Kotlin utilizando Kotlin 2.4.21 e JUnit Jupiter 6.0.3, devemos declarar as seguintes dependências:

  • A biblioteca padrão do Kotlin (kotlin-stdlib e kotlin-reflect para introspecção no Spring).
  • AssertJ Core (3.27.7, opcional mas altamente recomendado para asserções fluentes) e MockK (1.14.0).
  • As dependências do JUnit Jupiter 6.0.3 (junit-jupiter-api e junit-jupiter-engine).

Após especificarmos as dependências necessárias, a seção dependencies do nosso arquivo pom.xml fica da seguinte forma:

 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
29
30
31
32
33
34
35
36
37
38
<dependencies>
    <dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-stdlib</artifactId>
        <version>${kotlin.version}</version>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-reflect</artifactId>
        <version>${kotlin.version}</version>
    </dependency>

    <!-- Dependências de Teste -->
    <dependency>
        <groupId>org.assertj</groupId>
        <artifactId>assertj-core</artifactId>
        <version>${assertj.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.mockk</groupId>
        <artifactId>mockk-jvm</artifactId>
        <version>${mockk.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>${junit-jupiter.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>${junit-jupiter.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

E a seção equivalente dependencies no build.gradle.kts (Gradle 9.8.2 Kotlin DSL):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
dependencies {
    implementation(kotlin("stdlib"))
    implementation(kotlin("reflect"))
    implementation("org.springframework.boot:spring-boot-starter")

    // Dependências de Teste
    testImplementation("org.assertj:assertj-core:3.27.7")
    testImplementation("io.mockk:mockk:1.14.0")
    testImplementation("org.junit.jupiter:junit-jupiter-api:6.0.3")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:6.0.3")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

Terceiro, precisamos configurar os diretórios de código-fonte principal e de testes do nosso build. Utilizaremos os seguintes diretórios:

  • O diretório src/main/kotlin contém o código-fonte da nossa aplicação.
  • O diretório src/test/kotlin contém o código-fonte dos nossos testes unitários.

Após configurarmos esses diretórios, a seção build do nosso arquivo pom.xml fica da seguinte forma:

1
2
3
4
<build>
    <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
    <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
</build>

Quarto, precisamos garantir que o build compile nosso código Kotlin (e habilite o plugin spring / all-open do compilador Kotlin 2.4.21). Fazemos isso utilizando o kotlin-maven-plugin. Podemos configurar este plugin adicionando o seguinte trecho à seção plugins do nosso arquivo pom.xml:

 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
29
30
31
32
33
34
35
36
37
38
<plugin>
    <artifactId>kotlin-maven-plugin</artifactId>
    <groupId>org.jetbrains.kotlin</groupId>
    <version>${kotlin.version}</version>

    <configuration>
        <args>
            <arg>-Xjsr305=strict</arg>
        </args>
        <compilerPlugins>
            <plugin>spring</plugin>
        </compilerPlugins>
    </configuration>

    <executions>
        <execution>
            <id>compile</id>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>

        <execution>
            <id>test-compile</id>
            <goals>
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>

    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-allopen</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
    </dependencies>
</plugin>

Quinto, precisamos garantir que nossos testes unitários sejam executados na plataforma JUnit 6 utilizando o plugin Maven Surefire (maven-surefire-plugin versão 3.5.2). Fazemos isso adicionando o seguinte trecho à seção plugins do nosso arquivo pom.xml:

1
2
3
4
5
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.5.2</version>
</plugin>

Já configuramos o nosso build para testes unitários. No entanto, antes de podermos executar nossos testes unitários, precisamos escrever uma classe de teste e colocá-la no diretório src/test/kotlin. O código-fonte da nossa classe de teste MessageServiceTest fica da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.testwithspring.master.kotlin

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test

class MessageServiceTest {

    private lateinit var service: MessageService

    @BeforeEach
    fun configureSystemUnderTest() {
        service = MessageService()
    }

    @Test
    @DisplayName("Should return the correct message")
    fun shouldReturnCorrectMessage() {
        val message = service.getMessage()
        assertThat(message).isEqualTo("Hello World!")
    }
}

Agora podemos executar nossos testes utilizando o comando mvn clean test (ou ./gradlew clean test). Ao executarmos esse comando, veremos que o executor de testes executa nosso teste unitário:

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

Leitura Adicional:

Acabamos de criar um projeto que compila e executa nossos testes unitários em Kotlin 2.4.21. Contudo, nosso build ainda não possui suporte dedicado para testes de integração isolados. Vamos avançar e descobrir como suportar tanto testes unitários quanto testes de integração.

Executando Testes Unitários e de Integração

Como queremos executar tanto testes unitários quanto testes de integração de forma estruturada, precisamos fazer algumas alterações no nosso build. Vamos começar configurando os diretórios de código-fonte e de recursos (resources).

Configurando os Diretórios de Código-Fonte e Recursos

Podemos configurar os diretórios de código-fonte e de recursos dos nossos testes unitários e de integração seguindo estes passos:

Primeiro, precisamos adicionar o diretório de recursos dos nossos testes de integração ao nosso build. No Maven, fazemos isso utilizando o Build Helper Maven Plugin. Para utilizar esse plugin, seguimos estes passos:

  1. Adicionar o build-helper-maven-plugin ao nosso build.
  2. Criar uma execução que adiciona o diretório src/integration-test/resources ao ciclo de vida de recursos de teste.

Após configurarmos o Build Helper Maven Plugin, sua configuração fica da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <version>3.6.0</version>
    <executions>
        <execution>
            <id>add-integration-test-resources</id>
            <phase>generate-test-resources</phase>
            <goals>
                <goal>add-test-resource</goal>
            </goals>
            <configuration>
                <resources>
                    <resource>
                        <filtering>true</filtering>
                        <directory>src/integration-test/resources</directory>
                    </resource>
                </resources>
            </configuration>
        </execution>
    </executions>
</plugin>

Nota: Se os nossos testes de integração não possuírem arquivos de recursos (como scripts SQL ou datasets), podemos naturalmente pular esta etapa.

Leitura Adicional:

Segundo, como nossos testes unitários e de integração possuem diretórios de código-fonte separados, precisamos garantir que o compilador Kotlin compile o código-fonte de ambos os diretórios. Fazemos isso seguindo estes passos:

  1. Remover o elemento testSourceDirectory da seção build do nosso arquivo pom.xml.
  2. Garantir que o kotlin-maven-plugin compile o código-fonte encontrado nos diretórios src/test/kotlin e src/integration-test/kotlin.

Após fazermos essas alterações no nosso build, a configuração do kotlin-maven-plugin fica da seguinte forma:

 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
29
30
31
32
33
34
35
36
37
38
39
40
<plugin>
    <artifactId>kotlin-maven-plugin</artifactId>
    <groupId>org.jetbrains.kotlin</groupId>
    <version>${kotlin.version}</version>
    <configuration>
        <args>
            <arg>-Xjsr305=strict</arg>
        </args>
        <compilerPlugins>
            <plugin>spring</plugin>
        </compilerPlugins>
    </configuration>
    <executions>
        <execution>
            <id>compile</id>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
        <execution>
            <id>test-compile</id>
            <goals>
                <goal>test-compile</goal>
            </goals>
            <configuration>
                <sourceDirs>
                    <sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
                    <sourceDir>${project.basedir}/src/integration-test/kotlin</sourceDir>
                </sourceDirs>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-allopen</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
    </dependencies>
</plugin>

No Gradle 9.8.2 Kotlin DSL, essa mesma configuração de múltiplos diretórios de código-fonte e recursos no sourceSets.test é feita de forma concisa no build.gradle.kts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
sourceSets {
    test {
        kotlin {
            srcDirs("src/test/kotlin", "src/integration-test/kotlin")
        }
        resources {
            srcDirs("src/test/resources", "src/integration-test/resources")
        }
    }
}

Após configurarmos os diretórios de código-fonte e de recursos dos nossos testes unitários e de integração, precisamos garantir que possamos executar nossos testes de integração no build.

Executando Testes de Integração com o Maven Failsafe Plugin

Podemos executar nossos testes de integração utilizando o Maven Failsafe Plugin (versão 3.5.2). Adicionamos este plugin ao nosso build incluindo o seguinte trecho na seção plugins do nosso arquivo pom.xml:

1
2
3
4
5
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>3.5.2</version>
</plugin>

Após adicionarmos o Maven Failsafe Plugin ao nosso build, precisamos configurá-lo seguindo estes passos:

  1. Garantir que o Maven Failsafe Plugin execute todas as classes de teste cujo nome termine com o sufixo Test. Fazemos isso alterando o padrão curinga (wildcard pattern) utilizado para selecionar as classes de teste invocadas.
  2. Garantir que o Maven Failsafe Plugin execute ambos os goals integration-test e verify. O goal integration-test executa nossos testes de integração e o goal verify verifica os resultados dos testes de integração, falhando o build caso algum teste de integração tenha falhado.

Após configurarmos o Maven Failsafe Plugin, sua configuração fica da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>3.5.2</version>
    <configuration>
        <includes>
            <include>**/*Test.java</include>
        </includes>
    </configuration>
    <executions>
        <execution>
            <id>integration-tests</id>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Nota: Não utilizamos o padrão de inclusão default do Failsafe porque preferimos manter uma convenção de nomenclatura uniforme terminada em Test. Contudo, caso queira utilizar o padrão default, o Maven Failsafe Plugin inclui todas as classes de teste cujo nome começa com o prefixo IT ou termina com os sufixos IT ou ITCase.

Leitura Adicional:

Após configurarmos o Maven Failsafe Plugin, precisamos criar os perfis (profiles) que nos permitem executar apenas os testes unitários, apenas os testes de integração ou todos os testes. Vamos ver como criar esses perfis.

Criando os Perfis de Execução Necessários

Podemos criar os perfis de execução necessários seguindo estes passos:

Primeiro, precisamos especificar os valores padrão destas duas propriedades:

  • O valor da propriedade skip.integration.tests especifica se o nosso build deve pular os testes de integração. Como apenas os testes unitários devem ser executados por padrão, o valor default desta propriedade deve ser true.
  • O valor da propriedade skip.unit.tests define se o nosso build deve pular os testes unitários. Novamente, como apenas os testes unitários são executados por padrão, o valor default desta propriedade deve ser false.

Após especificarmos esses valores padrão, a seção properties do nosso arquivo pom.xml fica da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<properties>
    <kotlin.version>2.4.21</kotlin.version>
    <kotlin.compiler.incremental>true</kotlin.compiler.incremental>
    <kotlin.compiler.jvmTarget>21</kotlin.compiler.jvmTarget>
    <spring-boot.version>4.1.2</spring-boot.version>
    <junit-jupiter.version>6.0.3</junit-jupiter.version>
    <assertj.version>3.27.7</assertj.version>
    <mockk.version>1.14.0</mockk.version>

    <skip.integration.tests>true</skip.integration.tests>
    <skip.unit.tests>false</skip.unit.tests>
</properties>

Segundo, precisamos criar estes três perfis (profiles):

  • O perfil dev é o perfil padrão do nosso build e executa apenas os testes unitários.
  • O perfil integration-test executa apenas os testes de integração.
  • O perfil all-tests executa tanto os testes unitários quanto os testes de integração.

Após criarmos esses perfis, a seção profiles do nosso arquivo pom.xml fica da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<profiles>
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
    </profile>
    <profile>
        <id>integration-test</id>
        <properties>
            <skip.integration.tests>false</skip.integration.tests>
            <skip.unit.tests>true</skip.unit.tests>
        </properties>
    </profile>
    <profile>
        <id>all-tests</id>
        <properties>
            <skip.integration.tests>false</skip.integration.tests>
            <skip.unit.tests>false</skip.unit.tests>
        </properties>
    </profile>
</profiles>

Terceiro, precisamos garantir que o Maven Surefire Plugin não execute os testes unitários se o valor da propriedade skip.unit.tests for true. Após fazermos essa alteração na configuração do Maven Surefire Plugin, ela fica da seguinte forma:

1
2
3
4
5
6
7
8
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.5.2</version>
    <configuration>
        <skipTests>${skip.unit.tests}</skipTests>
    </configuration>
</plugin>

Quarto, precisamos garantir que o Maven Failsafe Plugin não execute os testes de integração se o valor da propriedade skip.integration.tests for true. Após fazermos essa alteração na configuração do Maven Failsafe Plugin, ela fica da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>3.5.2</version>
    <configuration>
        <includes>
            <include>**/*Test.java</include>
        </includes>
    </configuration>
    <executions>
        <execution>
            <id>integration-tests</id>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
            <configuration>
                <skipTests>${skip.integration.tests}</skipTests>
            </configuration>
        </execution>
    </executions>
</plugin>

Agora criamos os perfis necessários. A seguir, veremos como filtrar os testes invocados utilizando as tags do JUnit Jupiter 6.0.3.

Filtrando os Testes Invocados com Tags do JUnit 6

Como os plugins Maven Surefire e Failsafe (ou as tasks de teste no Gradle) compartilham o mesmo classpath de compilação de testes e o mesmo padrão de inclusão (**/*Test), precisamos filtrar os testes invocados utilizando tags do JUnit Jupiter (@Tag). Podemos fazer isso seguindo estes passos:

Primeiro, precisamos garantir que o Maven Surefire Plugin execute apenas os testes que possuem a tag unitTest. Após fazermos as alterações necessárias na configuração do Maven Surefire Plugin, ela fica da seguinte forma:

1
2
3
4
5
6
7
8
9
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.5.2</version>
    <configuration>
        <groups>unitTest</groups>
        <skipTests>${skip.unit.tests}</skipTests>
    </configuration>
</plugin>

Segundo, precisamos garantir que o Maven Failsafe Plugin execute apenas os testes que possuem a tag integrationTest. Após fazermos as alterações necessárias na configuração do Maven Failsafe Plugin, ela fica da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>3.5.2</version>
    <configuration>
        <groups>integrationTest</groups>
        <includes>
            <include>**/*Test.java</include>
        </includes>
    </configuration>
    <executions>
        <execution>
            <id>integration-tests</id>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
            <configuration>
                <skipTests>${skip.integration.tests}</skipTests>
            </configuration>
        </execution>
    </executions>
</plugin>

No Gradle 9.8.2 Kotlin DSL, filtramos as tags unitTest e integrationTest configurando a task test e registrando a task integrationTest no build.gradle.kts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
tasks.named<Test>("test") {
    useJUnitPlatform {
        includeTags("unitTest")
    }
}

val integrationTest by tasks.registering(Test::class) {
    description = "Runs integration tests."
    group = "verification"
    testClassesDirs = sourceSets["test"].output.classesDirs
    classpath = sourceSets["test"].runtimeClasspath
    useJUnitPlatform {
        includeTags("integrationTest")
    }
    shouldRunAfter(tasks.named("test"))
}

Terceiro, precisamos garantir que nosso teste unitário possua a tag unitTest. Após adicionarmos a anotação @Tag("unitTest") ao nosso teste unitário em src/test/kotlin, o código-fonte da nossa classe MessageServiceTest fica da seguinte forma:

 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
package com.testwithspring.master.kotlin

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test

@Tag("unitTest")
class MessageServiceTest {

    private lateinit var service: MessageService

    @BeforeEach
    fun configureSystemUnderTest() {
        service = MessageService()
    }

    @Test
    @DisplayName("Should return the correct message")
    fun shouldReturnCorrectMessage() {
        val message = service.getMessage()
        assertThat(message).isEqualTo("Hello World!")
    }
}

Quarto, precisamos escrever uma classe de teste de integração no diretório src/integration-test/kotlin que garanta que o sistema sob teste retorna a mensagem correta. Após escrevermos essa classe com a anotação @Tag("integrationTest"), seu código-fonte fica da seguinte forma:

 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
package com.testwithspring.master.kotlin

import org.assertj.core.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test

@Tag("integrationTest")
class GetMessageTest {

    private lateinit var service: MessageService

    @BeforeEach
    fun configureSystemUnderTest() {
        service = MessageService()
    }

    @Test
    @DisplayName("Should return the correct message")
    fun shouldReturnCorrectMessage() {
        val message = service.getMessage()
        Assertions.assertThat(message).isEqualTo("Hello World!")
    }
}

Leitura Adicional:

Agora adicionamos suporte completo a testes de integração ao nosso build. Vamos avançar e ver como executar nossos testes unitários e de integração na linha de comando.

Executando Nossos Testes

Se quisermos executar apenas os testes unitários, devemos executar o comando mvn clean test -P dev (ou ./gradlew clean test no Gradle 9.8.2). Quando executamos esse comando no terminal, vemos que apenas os nossos testes unitários são executados:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[INFO] --- maven-surefire-plugin:3.5.2:test (default-test) @ running-kotlin-tests-with-maven ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.testwithspring.master.kotlin.MessageServiceTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.046 s - in com.testwithspring.master.kotlin.MessageServiceTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

Se quisermos executar apenas os testes de integração, devemos executar o comando mvn clean verify -P integration-test (ou ./gradlew clean integrationTest no Gradle 9.8.2). Quando executamos esse comando no terminal, vemos que os testes unitários são ignorados e apenas os nossos testes de integração são executados:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[INFO] --- maven-surefire-plugin:3.5.2:test (default-test) @ running-kotlin-tests-with-maven ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- maven-failsafe-plugin:3.5.2:integration-test (integration-tests) @ running-kotlin-tests-with-maven ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.testwithspring.master.kotlin.GetMessageTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.043 s - in com.testwithspring.master.kotlin.GetMessageTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- maven-failsafe-plugin:3.5.2:verify (integration-tests) @ running-kotlin-tests-with-maven ---

Se quisermos executar tanto os testes unitários quanto os testes de integração, devemos executar o comando mvn clean verify -P all-tests (ou ./gradlew clean test integrationTest no Gradle 9.8.2). Quando executamos esse comando no terminal, vemos que tanto os testes unitários quanto os testes de integração são executados em sequência:

 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
[INFO] --- maven-surefire-plugin:3.5.2:test (default-test) @ running-kotlin-tests-with-maven ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.testwithspring.master.kotlin.MessageServiceTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.056 s - in com.testwithspring.master.kotlin.MessageServiceTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- maven-failsafe-plugin:3.5.2:integration-test (integration-tests) @ running-kotlin-tests-with-maven ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.testwithspring.master.kotlin.GetMessageTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.058 s - in com.testwithspring.master.kotlin.GetMessageTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- maven-failsafe-plugin:3.5.2:verify (integration-tests) @ running-kotlin-tests-with-maven ---

Agora sabemos configurar um build completo que compila e executa testes unitários e de integração utilizando Kotlin 2.4.21, plugins Spring e JUnit Jupiter 6.0.3. Vamos resumir o que aprendemos nesta aula.

Resumo

Esta aula nos ensinou quatro pontos essenciais:

  • Precisamos declarar a dependência da biblioteca padrão do Kotlin (kotlin-stdlib e kotlin-reflect) e habilitar o plugin spring (all-open) no compilador Kotlin 2.4.21.
  • Precisamos compilar o nosso código de teste (incluindo múltiplos diretórios como src/test/kotlin e src/integration-test/kotlin) utilizando o plugin de compilação do Kotlin (kotlin-maven-plugin ou sourceSets no Gradle 9.8.2 Kotlin DSL).
  • Se os nossos testes de integração possuírem arquivos de recursos (como scripts SQL ou datasets), precisamos adicionar o diretório src/integration-test/resources ao nosso build utilizando o Build Helper Maven Plugin (ou configurando resources.srcDirs no Gradle).
  • Podemos filtrar seletivamente os testes invocados combinando perfis de build (ou tasks dedicadas) com as tags @Tag("unitTest") e @Tag("integrationTest") do JUnit Jupiter 6.0.3.