Otimizando a Execução de Testes Kotlin no Gradle 9
Ao concluir esta aula, saberemos como criar e configurar um projeto Gradle 9.8.2 (utilizando Kotlin DSL) capaz de compilar e executar tanto testes unitários quanto testes de integração escritos em Kotlin 2.4.21 e JUnit Jupiter 6.0.3.
Esta aula assume que:
Os Requisitos do Nosso Build Gradle
Os requisitos do nosso build Gradle são:
- Todo o código (aplicação e testes) deve utilizar Kotlin 2.4.21.
- Os testes unitários e 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 isoladamente.
- Deve ser possível executar ambos os testes (unitários e de integração) em uma única execução.
- Se um teste de integração falhar, o nosso build também deve falhar.
- Os testes de integração e os testes unitários devem gerar relatórios HTML em diretórios diferentes.
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 testes unitários e de integração para a classe MessageService. Essa classe possui um único método chamado getMessage(), que retorna a mensagem: 'Hello World!'.
O código-fonte da classe MessageService (localizada em src/main/kotlin) é o seguinte:
|
|
A seguir, veremos como criar um projeto Kotlin simples no Gradle capaz de executar inicialmente apenas testes unitários.
Executando Testes Unitários com o Gradle
Quando queremos criar um projeto Kotlin que execute testes unitários no Gradle 9.8.2 com Kotlin DSL (build.gradle.kts), devemos seguir estes passos:
Primeiro, como estamos utilizando a JVM, devemos aplicar o plugin Gradle do Kotlin JVM (org.jetbrains.kotlin.jvm). Após aplicarmos este plugin, o nosso arquivo build.gradle.kts ficará assim:
|
|
Segundo, precisamos garantir que o Gradle utilize o repositório Maven Central ao resolver as dependências do nosso projeto Kotlin. Depois de configurarmos o repositório utilizado, o código-fonte do nosso arquivo build.gradle.kts ficará da seguinte forma:
|
|
Terceiro, devemos especificar as dependências da nossa aplicação. Como nossa aplicação utiliza Kotlin, declaramos a biblioteca padrão do Kotlin (kotlin-stdlib) na configuração de dependências implementation (nota: no Kotlin Gradle Plugin moderno, a kotlin-stdlib também é incluída por padrão, mas declará-la explicitamente deixa a árvore de dependências transparente).
Após fazermos isso, nosso arquivo build.gradle.kts terá a seguinte aparência:
|
|
Quarto, devemos especificar as dependências de teste. Como queremos escrever testes para uma aplicação Kotlin utilizando Kotlin 2.4.21, JUnit Jupiter 6.0.3 e AssertJ 3.27.7 (podendo também integrar MockK 1.14 para mocks idiomáticos quando necessário), podemos configurar as dependências de teste seguindo estes passos:
- Adicionar a dependência do AssertJ Core (opcional, mas recomendada para asserções fluentes) na configuração
testImplementation(que substituiu a antigatestCompileno Gradle moderno). - Adicionar a dependência da API do JUnit Jupiter (
junit-jupiter-api) na configuraçãotestImplementation. - Adicionar a dependência do motor de execução do JUnit Jupiter (
junit-jupiter-engine) e dojunit-platform-launcherna configuraçãotestRuntimeOnly(que substituiu a antigatestRuntime).
Depois de especificarmos as dependências de teste, o código-fonte do nosso arquivo build.gradle.kts ficará assim:
|
|
Quinto, devemos garantir que o Gradle execute nossos testes unitários utilizando a JUnit Platform (JUnit 6). Após configurarmos a task test, nosso arquivo build.gradle.kts ficará da seguinte maneira:
|
|
Agora já configuramos o nosso build Gradle. 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 é o seguinte:
|
|
Agora podemos executar nossos testes unitários utilizando o comando: ./gradlew clean test. Ao executarmos esse comando, vemos que o Gradle compila nossa aplicação e, em seguida, compila e executa nossos testes unitários:
|
|
Se você estiver utilizando o Gradle e quiser visualizar a lista detalhada de todas as tasks invocadas e logs de execução, execute o Gradle utilizando a opção de linha de comando
--info.Leitura Adicional:
Acabamos de criar um projeto Gradle capaz de compilar e executar nossos testes unitários. Contudo, nosso build ainda não possui suporte dedicado para testes de integração. Vamos avançar e descobrir como oferecer suporte tanto para testes unitários quanto para testes de integração.
Executando Testes Unitários e de Integração com o Gradle
Como queremos executar tanto testes unitários quanto testes de integração com o Gradle, precisamos fazer algumas alterações no nosso build. No Gradle legado (versões 5/6), costumava-se utilizar o plugin externo org.unbroken-dome.test-sets; no Gradle 9.8.2, podemos configurar conjuntos de testes (source sets) customizados ou utilizar o plugin nativo jvm-test-suite mantendo exatamente a mesma estrutura física de diretórios (src/integration-test/kotlin e src/integration-test/resources). Vamos realizar essas alterações passo a passo:
Primeiro, como queremos adicionar novos diretórios de código de teste e de recursos ao nosso build Gradle de forma estruturada, aplicamos o plugin jvm-test-suite (ou configuramos os sourceSets do plugin JVM). Após adicionarmos o plugin ao bloco plugins, nosso arquivo build.gradle.kts ficará assim:
|
|
Segundo, devemos configurar os diretórios de código-fonte e de recursos dos nossos testes de integração. Podemos configurar esses diretórios seguindo estes passos:
- Adicionar um novo conjunto de testes chamado
integrationTestao nosso build Gradle (herdando o classpath da aplicação principal e as dependências de teste). - Alterar o nome do diretório raiz desse conjunto de testes de
integrationTestparaintegration-test(src/integration-test/kotlinesrc/integration-test/resources).
Depois de configurarmos esses diretórios e a respectiva task integrationTest, o código-fonte do arquivo build.gradle.kts ficará da seguinte forma:
|
|
Esta configuração adiciona um source set chamado
integrationTestao nosso build e mapeia sua estrutura de pastas paraintegration-test. Isso significa que o diretóriosrc/integration-test/kotlincontém o código-fonte dos nossos testes de integração e o diretóriosrc/integration-test/resourcescontém os recursos (resources) dos nossos testes de integração. Além disso, por ser uma taskTestseparada (integrationTest), o Gradle gera automaticamente relatórios HTML em diretórios distintos (build/reports/tests/testebuild/reports/tests/integrationTest).
Terceiro, devemos garantir que os testes unitários sejam executados antes dos testes de integração (shouldRunAfter / mustRunAfter). Após adicionarmos a configuração necessária ao nosso arquivo build.gradle.kts, ele ficará assim:
|
|
Quarto, devemos garantir que os testes de integração sejam executados quando invocarmos a task build (através da task de ciclo de vida check). Além disso, precisamos garantir que o nosso build falhe caso algum teste de integração falhe. Após adicionarmos a configuração necessária ao nosso arquivo build.gradle.kts, ele terá a seguinte aparência:
|
|
Quinto, devemos garantir que o Gradle execute nossos testes de integração utilizando o JUnit Jupiter (JUnit Platform). Depois de configurarmos useJUnitPlatform() na task integrationTest, nosso arquivo build.gradle.kts ficará assim:
|
|
Sexto, devemos filtrar os testes invocados utilizando tags do JUnit Jupiter (@Tag). Podemos fazer isso seguindo estes passos:
- Garantir que o JUnit execute apenas os testes que possuem a tag
unitTestquando executarmos nossos testes unitários. - Garantir que o JUnit execute apenas os testes que possuem a tag
integrationTestquando executarmos nossos testes de integração.
Após configurarmos a filtragem dos testes invocados, o código-fonte final do nosso arquivo build.gradle.kts ficará da seguinte forma:
|
|
Agora temos um build Gradle completo que compila e executa nossos testes unitários e de integração. A seguir, veremos como escrever os testes unitários e de integração que serão executados pelo Gradle.
Escrevendo Testes Unitários e de Integração
Podemos escrever nossos testes unitários e de integração seguindo estes passos:
Primeiro, como já escrevemos nossa classe de teste unitário anteriormente, a única coisa que precisamos fazer é adicionar a anotação @Tag("unitTest") à nossa classe de teste unitário (localizada em src/test/kotlin).
Após adicionarmos essa tag à nossa classe de teste unitário, seu código-fonte ficará assim:
|
|
Segundo, devemos escrever uma classe de teste de integração (localizada em src/integration-test/kotlin) que garanta que o sistema sob teste retorne a mensagem correta. Além disso, devemos adicionar a anotação @Tag("integrationTest") à nossa classe de teste de integração.
Depois de escrevermos nossa classe de teste de integração, seu código-fonte ficará da seguinte maneira:
|
|
Leitura Adicional:
Agora já escrevemos nossos testes unitários e de integração. Vamos em frente e descobrir como executá-los no terminal.
Executando Nossos Testes
Se quisermos executar apenas os testes unitários, devemos rodar o comando: ./gradlew clean test. Quando executamos esse comando no terminal, vemos que o Gradle compila e executa apenas os nossos testes unitários:
|
|
Se quisermos executar apenas os testes de integração, devemos rodar o comando: ./gradlew clean integrationTest. Quando executamos esse comando no terminal, vemos que o Gradle compila e executa apenas os nossos testes de integração:
|
|
Se quisermos executar tanto os testes unitários quanto os testes de integração, podemos rodar um destes dois comandos: ./gradlew clean test integrationTest ou ./gradlew clean build. Quando executamos a task build, vemos que o Gradle executa primeiro os testes unitários e, em seguida, os testes de integração:
|
|
Agora sabemos como criar um build Gradle moderno que compila e executa testes unitários e de integração utilizando Kotlin e JUnit Jupiter. Vamos resumir o que aprendemos nesta aula.
Resumo
Esta aula nos ensinou cinco pontos fundamentais:
- Como estamos utilizando a JVM, devemos aplicar o plugin Gradle do Kotlin JVM (
kotlin("jvm")). - Declaramos a biblioteca padrão do Kotlin (
kotlin-stdlib) na configuração de dependênciasimplementatione as bibliotecas de teste (junit-jupiter-api,assertj-core) emtestImplementation. - Podemos adicionar novos diretórios de código-fonte e recursos de teste (
src/integration-test/kotlinesrc/integration-test/resources) ao nosso build Gradle configurando source sets dedicados e tasks do tipoTest. - Para executar testes que utilizam o JUnit Jupiter (JUnit 6), precisamos habilitar o suporte nativo à JUnit Platform no Gradle com
useJUnitPlatform(). - Podemos filtrar com segurança quais testes são invocados em cada fase do build utilizando tags do JUnit (
@TageincludeTags).