Compartilhando Fixtures Entre Módulos com java-test-fixtures

Ao final desta aula, seremos capazes de:

  • Compreender por que é uma excelente ideia utilizar os mesmos datasets de teste (DbUnit / SQL fixtures) a partir de múltiplos módulos em uma arquitetura corporativa.
  • Saber como empacotar e publicar nossos datasets de teste em um repositório de artefatos (Maven repository) ou expô-los via java-test-fixtures.
  • Consumir os datasets de teste publicados ou compartilhados a partir de outro módulo do projeto.
  • Compreender as melhores práticas que tornam nosso trabalho muito mais simples ao manter datasets e constantes de teste compartilhados entre múltiplos módulos.

Esta aula assume que:

  • Você já está familiarizado com o uso de datasets de teste (DbUnit / @DatabaseSetup / @Sql) em testes de integração.
  • Você sabe escrever testes de integração para uma aplicação web Spring com Kotlin 2.4.21 e JUnit Jupiter 6.0.3 no Spring Boot 4.1.2.

Por Que Deveríamos Nos Importar?

Se estivermos trabalhando em um projeto corporativo composto por múltiplos módulos (seja em um repositório multi-módulo ou em múltiplos projetos que evoluem sobre o mesmo domínio), é bastante comum que esses diferentes módulos acessem as mesmas tabelas do banco de dados. Quando estamos nessa situação, devemos utilizar os mesmos datasets de teste (como arquivos do DbUnit ou fixtures compartilhadas) ao escrever testes de integração para esses módulos, pois essa abordagem torna nossos testes de integração muito mais fáceis de escrever e manter.

Para sermos mais específicos, essa abordagem oferece os seguintes benefícios:

  • Não precisamos criar nossos dados de teste do zero toda vez que escrevemos testes de integração para um novo módulo. Podemos simplesmente reutilizar os datasets que contêm os dados de teste compartilhados.
  • Se fizermos alterações no nosso modelo de dados (schema do banco de dados), precisaremos atualizar apenas um único módulo/projeto centralizado.
  • Se quisermos alterar os dados de teste compartilhados, precisaremos modificar apenas um único lugar.

Em resumo, é uma ótima prática utilizar datasets de teste compartilhados porque realizar alterações em apenas um único ponto é muito mais rápido — e consideravelmente menos arriscado — do que replicar exatamente as mesmas alterações manualmente em múltiplos módulos.

A seguir, veremos como podemos empacotar e publicar nossos datasets de teste em um repositório Maven (e como o plugin java-test-fixtures do Gradle 9.8.2 moderniza esse compartilhamento).

Publicando Nossos Datasets de Teste em um Repositório Maven

Quando queremos utilizar os mesmos datasets do DbUnit (e classes auxiliares de fixtures) a partir de múltiplos módulos ou projetos independentes, o primeiro passo é empacotar e disponibilizar esses datasets — seja publicando um artefato JAR em um repositório Maven ou expondo o capability de testFixtures no Gradle. Podemos publicar nossos datasets seguindo estes passos:

Primeiro, precisamos criar um projeto ou módulo dedicado que contenha nossos datasets de teste (test-data-sets).

Em um projeto Maven clássico, o arquivo pom.xml desse módulo dedicado se parece com o seguinte:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.testwithspring.master</groupId>
    <artifactId>test-data-sets</artifactId>
    <version>0.1</version>
    <name>Test Data Sets</name>
    <description>
        This example demonstrates how you can package DbUnit data sets
        into a jar file.
    </description>
</project>

Na stack moderna com Gradle 9.8.2 (Kotlin DSL) e Kotlin 2.4.21, podemos configurar esse módulo test-data-sets utilizando o plugin maven-publish (para publicar o artefato em um repositório Maven) em conjunto com o plugin nativo java-test-fixtures (quando também desejamos expor fixtures diretamente entre submódulos):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
plugins {
    kotlin("jvm") version "2.4.21"
    `java-test-fixtures`
    `maven-publish`
}

group = "com.testwithspring.master"
version = "0.1"
description = "This example demonstrates how you can package DbUnit data sets into a jar file."

publishing {
    publications {
        create<MavenPublication>("mavenJava") {
            from(components["java"])
        }
    }
}

Segundo, precisamos colocar nossos arquivos de dataset do DbUnit no diretório src/main/resources (ou src/testFixtures/resources quando utilizamos exclusivamente o source set de testFixtures do Gradle). Colocar os arquivos em src/main/resources do módulo test-data-sets garante que nossos datasets sejam empacotados diretamente dentro do arquivo JAR gerado.

A estrutura de diretórios a seguir ilustra os datasets do DbUnit localizados no diretório src/main/resources da nossa aplicação de exemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
test-data-sets/
└── src/
    └── main/
        └── resources/
            └── com/
                └── testwithspring/
                    └── master/
                        ├── no-tasks-and-tags.xml
                        ├── tasks.xml
                        └── users.xml

Terceiro, precisamos empacotar nossos datasets em um arquivo JAR e publicar esse arquivo JAR em um repositório Maven. Como este é um exemplo didático, publicaremos nosso arquivo JAR no nosso repositório Maven local (~/.m2/repository). Podemos fazer isso executando o comando mvn clean install (ou ./gradlew :test-data-sets:publishToMavenLocal no Gradle 9.8.2) no terminal.

Nota: Se você estiver trabalhando em um projeto de software do mundo real, você deve publicar o arquivo JAR em um repositório Maven privado (como Nexus, Artifactory ou GitHub Packages) que possa ser acessado apenas a partir da rede privada da sua organização. O benefício dessa abordagem é que você não precisa publicar o arquivo JAR manualmente no seu repositório Maven local toda vez que alguém fizer alterações nos dados de teste. Em vez disso, basta atualizar o número da versão da dependência correspondente.

Vamos prosseguir e descobrir como podemos utilizar nossos datasets do DbUnit a partir de outro módulo.

Utilizando Nossos Datasets de Teste a Partir de Outro Módulo

Quando queremos utilizar nossos datasets do DbUnit a partir de outro módulo consumidor, devemos seguir estes passos:

Primeiro, precisamos garantir que nossos datasets de teste sejam encontrados no classpath de execução dos testes.

Em um módulo configurado com pom.xml no Maven, declaramos a seguinte dependência com escopo test:

1
2
3
4
5
6
<dependency>
    <groupId>com.testwithspring.master</groupId>
    <artifactId>test-data-sets</artifactId>
    <version>0.1</version>
    <scope>test</scope>
</dependency>

No Gradle 9.8.2 (Kotlin DSL), declaramos a dependência no bloco dependencies do build.gradle.kts do módulo consumidor (podendo consumir tanto o artefato publicado no repositório Maven quanto o testFixtures de outro submódulo):

1
2
3
4
5
6
7
dependencies {
    // Consumindo o artefato publicado em um repositório Maven:
    testImplementation("com.testwithspring.master:test-data-sets:0.1")

    // Ou consumindo diretamente via plugin java-test-fixtures em um build multi-módulo:
    // testImplementation(testFixtures(project(":test-data-sets")))
}

Segundo, como os datasets compartilhados do DbUnit agora são encontrados na raiz do classpath de testes, podemos utilizá-los exatamente da mesma maneira que utilizamos datasets “locais”.

Por exemplo, vamos configurar os dados de teste utilizados por um teste de integração que garante que o objeto TaskRepository funciona conforme o esperado ao criar uma nova tarefa (Task). Depois de configurarmos os dados de teste com Kotlin 2.4.21, Spring Boot 4.1.2 e JUnit Jupiter 6.0.3, o código-fonte da nossa classe de teste 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
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.DatabaseIntegrationTest
import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate

@DatabaseIntegrationTest
@ExtendWith(SoftAssertionsExtension::class)
@DatabaseSetup(
    value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ]
)
@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
@DisplayName("Create a new task")
internal class CreateNewTaskTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val repository: TaskRepository
) {

}

A seguir, examinaremos algumas boas práticas fundamentais que tornam nossa vida muito mais simples quando trabalhamos com datasets de teste utilizados por múltiplos módulos.

Boas Práticas

Podemos facilitar consideravelmente a manutenção da nossa suíte de testes seguindo estas regras:

  • Se fizermos quaisquer alterações no projeto/módulo que contém nossos datasets do DbUnit, devemos incrementar o número da sua versão. Isso garante que nossas alterações não quebrem inesperadamente os outros módulos que já dependem de uma versão anterior dos nossos datasets.
  • Se utilizarmos classes ou objetos de constantes (object Tasks, object Users) que espelham os dados de teste armazenados no banco de dados, devemos colocar essas classes no mesmo projeto/módulo que contém nossos datasets do DbUnit. Além disso, devemos colocar essas classes no diretório src/main/kotlin (ou src/main/java / src/testFixtures/kotlin) para garantir que elas sejam empacotadas junto ao arquivo JAR gerado. Essa abordagem torna nossos dados de teste muito mais fáceis de manter: por exemplo, se precisarmos alterar um valor nos dados de teste, modificaremos apenas um único projeto.
  • Devemos atualizar nossos datasets do DbUnit toda vez que fizermos alterações no nosso modelo de dados (migrações de banco de dados). Isso garante que mudanças estruturais no modelo de dados não quebrem silenciosamente nossos testes de integração.
  • O número da versão do nosso modelo de dados deve estar refletido ou incluído no número da versão da dependência que fornece os dados de teste para nossos testes de integração (por exemplo, <versão-do-schema>.<versão-do-dataset>). Isso economiza muito tempo porque não precisamos adivinhar qual versão do pacote de datasets é compatível com a versão atual do nosso modelo de dados.
  • Os datasets do DbUnit que são compartilhados por múltiplos módulos não devem conter dados de teste específicos de um único módulo, pois adicionar cenários particulares de apenas um módulo a esses arquivos compartilhados os tornará difíceis de ler, entender e manter. Se precisarmos de dados de teste específicos para um cenário isolado de um módulo, devemos colocar esses datasets localmente dentro do próprio módulo que os utiliza.

Agora já sabemos como publicar datasets de teste em um repositório Maven (ou compartilhá-los via java-test-fixtures) e consumi-los a partir de outros módulos. Vamos resumir o que aprendemos nesta aula.

Resumo

Esta aula nos ensinou cinco pontos essenciais:

  • É uma excelente prática utilizar datasets de teste compartilhados porque realizar alterações em apenas um único lugar é muito mais rápido (e menos arriscado) do que replicar as mesmas modificações em múltiplos módulos.
  • Se quisermos utilizar datasets compartilhados entre projetos distintos, devemos empacotar e publicar esses datasets em um repositório Maven (ou expô-los via java-test-fixtures no Gradle).
  • Como os datasets compartilhados ficam disponíveis no classpath de testes, podemos referenciá-los nas nossas anotações de teste exatamente da mesma forma que referenciamos datasets “locais”.
  • O número da versão do nosso modelo de dados deve ser incluído no número da versão da dependência que fornece os dados de teste para os nossos testes de integração.
  • Não devemos adicionar dados de teste específicos de um único módulo aos datasets que são compartilhados por múltiplos módulos.