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:
|
|
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.versionespecifica a versão utilizada do Kotlin (2.4.21). - A propriedade
kotlin.compiler.incrementalhabilita a compilação incremental no compilador Kotlin. - A propriedade
kotlin.compiler.jvmTargetdefine o bytecode alvo da JVM (21). - A propriedade
junit-jupiter.versionespecifica a versão utilizada do JUnit 6 (6.0.3). - A propriedade
assertj.versionespecifica a versão do AssertJ (3.27.7). - A propriedade
mockk.versionespecifica 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:
|
|
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:
|
|
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-stdlibekotlin-reflectpara 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-apiejunit-jupiter-engine).
Após especificarmos as dependências necessárias, a seção dependencies do nosso arquivo pom.xml fica da seguinte forma:
|
|
E a seção equivalente dependencies no build.gradle.kts (Gradle 9.8.2 Kotlin DSL):
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
- Adicionar o
build-helper-maven-pluginao nosso build. - 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:
|
|
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:
- Remover o elemento
testSourceDirectoryda seçãobuilddo nosso arquivopom.xml. - Garantir que o
kotlin-maven-plugincompile 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:
|
|
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:
|
|
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:
|
|
Após adicionarmos o Maven Failsafe Plugin ao nosso build, precisamos configurá-lo seguindo estes passos:
- 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. - Garantir que o Maven Failsafe Plugin execute ambos os goals
integration-testeverify. O goalintegration-testexecuta nossos testes de integração e o goalverifyverifica 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:
|
|
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 prefixoITou termina com os sufixosITouITCase.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.testsespecifica 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 sertrue. - O valor da propriedade
skip.unit.testsdefine 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 serfalse.
Após especificarmos esses valores padrão, a seção properties do nosso arquivo pom.xml fica da seguinte forma:
|
|
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-testexecuta apenas os testes de integração. - O perfil
all-testsexecuta 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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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-stdlibekotlin-reflect) e habilitar o pluginspring(all-open) no compilador Kotlin 2.4.21. - Precisamos compilar o nosso código de teste (incluindo múltiplos diretórios como
src/test/kotlinesrc/integration-test/kotlin) utilizando o plugin de compilação do Kotlin (kotlin-maven-pluginousourceSetsno 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/resourcesao nosso build utilizando o Build Helper Maven Plugin (ou configurandoresources.srcDirsno 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.