Configurando a Task e2eTest para Kotest no Gradle 9

Depois de concluirmos esta aula, nós:

  • Saberemos como configurar os diretórios de código-fonte e de recursos dos nossos testes End-to-End no Gradle 9.
  • Saberemos iniciar nossa aplicação web antes da execução dos nossos testes End-to-End e pará-la após a conclusão dos testes.
  • Compreenderemos como compilar e executar testes End-to-End que utilizam o Kotest Framework e Playwright no Gradle 9.

Esta aula assume que:

  • Você está familiarizado com tags e categorias do JUnit Platform / Kotest.
  • Você sabe executar testes de integração que utilizam o Kotest Framework com o Gradle 9.
  • Você está familiarizado com o Kotest Framework.
  • Você sabe criar um projeto Gradle que executa testes End-to-End com JUnit 6.

Os Requisitos do Nosso Build Gradle

Os requisitos do nosso build Gradle 9 (build.gradle.kts) são:

  • Nossos testes End-to-End devem possuir um diretório de código-fonte separado. O diretório src/end-to-end-test/kotlin deve conter o código-fonte das nossas especificações End-to-End.
  • Nossos testes End-to-End devem possuir um diretório de recursos separado. O diretório src/end-to-end-test/resources deve conter os recursos dos nossos testes End-to-End.
  • Devemos ser capazes de executar apenas os testes unitários (test), apenas os testes de integração (integrationTest), apenas os testes End-to-End (endToEndTest) ou todos os testes (check / build).
  • Se um teste End-to-End falhar, nosso build também deve falhar.
  • Testes unitários, de integração e End-to-End devem possuir diretórios de relatórios HTML separados (build/reports/tests/test, build/reports/tests/integrationTest e build/reports/tests/endToEndTest).

A seguir, descobriremos como podemos configurar os diretórios de código-fonte e de recursos dos nossos testes End-to-End no Gradle 9.

Configurando os Diretórios de Código-Fonte e Recursos dos Nossos Testes End-to-End

No Gradle moderno (Gradle 9 com Kotlin DSL), configuramos os diretórios de código-fonte e recursos dos nossos testes End-to-End utilizando o plugin nativo jvm-test-suite (que substituiu o antigo plugin de terceiros org.unbroken-dome.test-sets).

Como já aplicamos este plugin para configurar a suíte integrationTest, podemos configurar os diretórios de código-fonte e recursos dos nossos testes End-to-End registrando uma nova suíte chamada endToEndTest no bloco testing.suites. Quando adicionamos esta suíte ao nosso build Gradle, garantimos que o diretório raiz do source set criado aponte para src/end-to-end-test.

Após termos adicionado a nova suíte de testes ao nosso build.gradle.kts, ele fica assim:

 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
41
42
43
44
45
46
47
48
49
50
51
52
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"
    `jvm-test-suite`
}

testing {
    suites {
        val test by getting(JvmTestSuite::class) {
            useJUnitJupiter("6.0.3")
        }

        val integrationTest by registering(JvmTestSuite::class) {
            useJUnitJupiter("6.0.3")
            sources {
                kotlin {
                    setSrcDirs(listOf("src/integration-test/kotlin"))
                }
                resources {
                    setSrcDirs(listOf("src/integration-test/resources"))
                }
            }
            dependencies {
                implementation(project())
            }
        }

        val endToEndTest by registering(JvmTestSuite::class) {
            useJUnitJupiter("6.0.3")
            sources {
                kotlin {
                    setSrcDirs(listOf("src/end-to-end-test/kotlin"))
                }
                resources {
                    setSrcDirs(listOf("src/end-to-end-test/resources"))
                }
            }
            dependencies {
                implementation(project())
                implementation("com.microsoft.playwright:playwright:1.51.0")
            }
        }
    }
}

// Faz com que integrationTest e endToEndTest herdem as dependências de testImplementation (Kotest, Spring Boot Test, Testcontainers)
configurations["integrationTestImplementation"].extendsFrom(configurations.testImplementation.get())
configurations["integrationTestRuntimeOnly"].extendsFrom(configurations.testRuntimeOnly.get())
configurations["endToEndTestImplementation"].extendsFrom(configurations.testImplementation.get())
configurations["endToEndTestRuntimeOnly"].extendsFrom(configurations.testRuntimeOnly.get())

Esta configuração adiciona um source set chamado endToEndTest ao nosso build e configura seus diretórios raiz: o diretório src/end-to-end-test/kotlin contém o código-fonte das nossas especificações End-to-End e o diretório src/end-to-end-test/resources contém os recursos dos nossos testes End-to-End. Além disso, o plugin jvm-test-suite já configura automaticamente diretórios de relatórios HTML isolados para cada task de teste em build/reports/tests/<taskName>.

Vamos avançar e descobrir como podemos configurar a task que executa nossos testes End-to-End.

Configurando a Task que Executa Nossos Testes End-to-End

Quando adicionamos a suíte endToEndTest ao nosso build, o plugin jvm-test-suite criou automaticamente uma task chamada endToEndTest que executa nossos testes End-to-End. Contudo, para atender a todos os requisitos do nosso build Gradle, precisamos fazer algumas alterações em sua configuração seguindo estes passos:

Primeiro, temos que garantir que os testes de integração sejam executados antes dos testes End-to-End e que os testes End-to-End sejam executados quando invocarmos a task check (ou build). Podemos fazer isso seguindo estes passos:

  1. Garantir que nossos testes End-to-End sejam executados como dependência da task check, fazendo o build falhar caso um teste End-to-End falhe.
  2. Garantir que nossos testes de integração (integrationTest) sejam executados antes dos nossos testes End-to-End (shouldRunAfter(tasks.named("integrationTest"))). Isso garante que nossos testes de integração sejam executados mesmo que nossos testes End-to-End falhem.

Segundo, temos que configurar a task endToEndTest seguindo estes passos:

  1. Garantir que nossos testes End-to-End sejam executados pelo JUnit Platform (useJUnitPlatform).
  2. Configurar o JUnit Platform para executar apenas as especificações que pertencem à tag/categoria EndToEndTest (includeTags("endToEndTest")).
  3. Garantir que o Gradle imprima as informações escritas em System.out ou System.err quando nossos testes End-to-End forem executados (testLogging { showStandardStreams = true }).

Após termos configurado as tasks test, integrationTest e endToEndTest, nosso arquivo build.gradle.kts fica assim:

 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
tasks.named<Test>("test") {
    useJUnitPlatform {
        includeTags("unitTest")
    }
    testLogging {
        showStandardStreams = true
    }
}

tasks.named<Test>("integrationTest") {
    shouldRunAfter(tasks.named("test"))
    useJUnitPlatform {
        includeTags("integrationTest")
    }
    testLogging {
        showStandardStreams = true
    }
}

tasks.named<Test>("endToEndTest") {
    shouldRunAfter(tasks.named("integrationTest"))
    useJUnitPlatform {
        includeTags("endToEndTest")
    }
    testLogging {
        showStandardStreams = true
    }
}

tasks.named("check") {
    dependsOn(tasks.named("integrationTest"))
    dependsOn(tasks.named("endToEndTest"))
}

Vamos avançar e entender como iniciamos e paramos nossa aplicação web.

Iniciando e Parando Nossa Aplicação Web

Antes de podermos executar nossos testes End-to-End, temos que iniciar nossa aplicação web antes da execução das especificações e pará-la após a conclusão.

  • Em aplicações Spring Boot 4: Podemos iniciar e parar nossa aplicação diretamente através do suporte de testes do Spring Boot (@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) ou DEFINED_PORT) em conjunto com o SpringExtension do Kotest e o Testcontainers! Isso elimina a necessidade de plugins externos de container servlet como o Gretty no build.gradle.kts, tornando o build mais rápido, autocontido e livre de conflitos de portas.
  • Em aplicações web legadas sem Spring Boot: Caso a aplicação seja empacotada como WAR clássico sem Spring Boot, um plugin de container servlet pode iniciar o servidor antes da task endToEndTest e pará-lo ao final.

Agora estamos prontos para executar nossos testes End-to-End. Vamos descobrir como podemos fazer isso.

Executando Testes End-to-End

Podemos executar nossos testes End-to-End utilizando uma das seguintes opções:

Primeiro, podemos executar apenas os nossos testes End-to-End utilizando o comando:

1
./gradlew clean endToEndTest

Quando executamos este comando no terminal, vemos uma saída semelhante a esta:

1
2
3
4
5
6
> Task :endToEndTest

com.testwithspring.master.message.ShowMessageTextSpec > Given a pagina de mensagem When o usuario abre a pagina Then deve exibir o titulo correto STANDARD_OUT
    End-to-End Test: Should show the correct title

BUILD SUCCESSFUL in 9s

Esta saída nos revela que:

  1. O ambiente da aplicação web (e o banco PostgreSQL via Testcontainers) é iniciado para a suíte endToEndTest.
  2. O Gradle executa apenas nossas especificações End-to-End quando invocamos a task endToEndTest.
  3. O servidor web e o navegador Playwright são encerrados automaticamente após a conclusão da task.

Segundo, se quisermos executar todos os testes na ordem correta (unitários -> integração -> End-to-End), podemos usar uma destas duas opções:

  1. Executar explicitamente as três tasks:
    1
    
    ./gradlew clean test integrationTest endToEndTest
  2. Executar o build completo do projeto:
    1
    
    ./gradlew clean build

Quando executamos todos os testes, vemos as três tasks executando em sequência ordenada:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
> Task :test
com.testwithspring.master.message.MessageControllerSpec > Show Message PASSED

> Task :integrationTest
com.testwithspring.master.message.ShowMessageSpec > Show Message PASSED

> Task :endToEndTest
com.testwithspring.master.message.ShowMessageTextSpec > Open message page PASSED

BUILD SUCCESSFUL in 14s

Vamos resumir o que aprendemos nesta aula.

Resumo

Esta aula nos ensinou três coisas:

  • Podemos configurar diretórios de código-fonte (src/end-to-end-test/kotlin) e recursos (src/end-to-end-test/resources) isolados no Gradle 9 usando o plugin nativo jvm-test-suite.
  • Se estivermos usando o Spring Boot 4, podemos iniciar e parar nossa aplicação web usando seu próprio suporte de testes (@SpringBootTest).
  • Podemos executar tanto testes de integração quanto testes End-to-End de forma ordenada e com relatórios HTML separados usando as tasks integrationTest e endToEndTest.