Pular para conteúdo

Como buildar e testar

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-15

Audiência: dev que entra no projeto e precisa rodar, buildar e testar localmente.

Requisitos

  • Java 25, preferencialmente GraalVM, no JAVA_HOME (toolchain pinada no build.gradle.kts: JavaLanguageVersion.of(25); instale via SDKMAN).
  • Gradle 9.5.1 — via wrapper ./gradlew (exigido pelo Java 25; o 8.10.x anterior não roda em JDK 25). Nunca Maven.
  • PostgreSQL 18+ — necessário para a função nativa uuidv7() (as PKs bi_* são UUID v7). Flyway 12+ para suporte a PG18.
  • Docker em execução — o banco (Postgres) roda em container no dev local, e os testes de integração (Testcontainers) sobem Postgres/Mongo/Garage reais.

Atenção ao launcher do Gradle: ele roda no JDK do ambiente. O Gradle 9.5.1 exige Java ≤25 e não roda em JDK futuro não suportado — se JAVA_HOME apontar para uma JVM que o Gradle não reconhece, o build falha com IllegalArgumentException: <versão>.

Recomendações de ambiente

  • VS Code para editar (extensões de Java/Gradle ajudam).
  • Claude Code — plugin no VS Code ou CLI no terminal; as regras de IA do repo estão no CLAUDE.md.
  • Forgejo configurado no repo (remote + credencial) para issues e PRs.
  • SDKMAN para instalar/alternar JDKs (a GraalVM 25 do projeto).

Rodar e buildar

./gradlew run          # sobe o app local (precisa de um Postgres 18+ alcançável)
./gradlew shadowJar    # gera build/libs/app.jar (deploy via Dockerfile no Coolify)
./gradlew check        # compila + Checkstyle + testes, integração inclusa (Docker rodando)

Build tool: Gradle (./gradlew) — nunca Maven. Lint estático: Checkstyle 10.20.1 (regras X-Adm comuns; roda no check).

As camadas de teste

Camada O que cobre Como roda
Unitário lógica pura (detector, parsers, conversores, limites) ./gradlew test
Conformidade golden (Java×Python) paridade do parse Java com o resultado do fluxo Python legado, por tipo de arquivo ./gradlew test — compara contra fixtures golden
Integração (Testcontainers) repositórios, BulkUpserter diff-aware, storage Garage, nuke, rotas autenticadas — contra Postgres/Mongo/Garage reais ./gradlew test (@IntegracaoDocker — Docker rodando)
Arquitetura (ArchUnit) as travas do guia do código, pela fábrica RegrasArquitetura da xadm-comum-teste ./gradlew test
./gradlew test                 # tudo: unitário + conformidade + ArchUnit + integração
./gradlew check                # o gate: test + checkstyle + cobertura (o que a CI roda)

A integração (@IntegracaoDocker — Testcontainers + Postgres/Mongo/Garage reais) roda por default: é o que o gate da CI executa (docker nativo do runner). A anotação e a condição vêm da xadm-comum-teste (br.com.xadm.comum.teste.IntegracaoDocker). Sem Docker rodando, a DockerDisponivelCondition pula cada classe com ERROR no log e no relatório do JUnit, que a marca skipped — o verde dessa rodada não prova a integração. Integração atrás de flag seria cobertura órfã: não há opt-in.

Infra de teste

  • Postgres: o singleton da xadm-comum-teste (PostgresTestResource, postgres:18-alpine em tmpfs), ligado ao contexto pelo PostgresTestPropertyProvider. O teste que precisa de estado próprio (replicação lógica, flag do driver) sobe container dedicado.
  • Garage: o GarageTestResource da lib, com o bucket onpetro (garage.test.bucket na task test). Teste que não exercita storage fixa app.storage.modo=PSQL.
  • Limpeza entre testes: o AbstractRepositoryIntegrationTest apaga as tabelas bi_*/xls_* que os testes gravam, depois de esperar o processamento assíncrono pendente. Não é a IntegracaoComPostgres da lib porque o TRUNCATE de todas as tabelas apagaria o seed da bi_configuracao.
  • Travas: a task test falha em 15 min, cada teste em 2 min (junit-platform.properties, desligado no debug) e o pool de teste desiste de um banco morto em 5 s (application-test.yml).

Filtro por classe:

./gradlew test --tests "*Relatorio18ProcessorTest*"

@Replaces declarado em classe de teste vale para o source-set INTEIRO

Um fake @Singleton @Replaces(Servico.class) escrito como classe aninhada dentro de um teste não fica restrito àquele teste: o processador de anotações gera uma bean definition global, e todo @MicronautTest que injetar Servico recebe o fake.

Regra: todo fake @Replaces fica atrás de um @Requires(property = …) e cada teste que o quer liga a property no seu getProperties() — ver AdminControllerTest.FAKES.

Como flagrar: cobertura perto de zero numa classe que "tem teste de integração verde" é o sintoma. Confirme sabotando uma asserção (o teste tem de ficar vermelho) ou procurando no log de teste uma linha que o código real emitiria.

Invocação parcial sobrescreve a cobertura

jacocoTestReport depende de test, e o filtro faz parte do input da task: rodar o relatório depois de um --tests (ou sem Docker) re-executa test com a rodada menor e sobrescreve o test.exec. Os XMLs antigos em build/test-results/ continuam no disco, o que dá a falsa impressão de que os testes rodaram. Meça sempre numa invocação só, com Docker:

./gradlew test jacocoTestReport imprimirCobertura

O relatório conta só código autoral: o jacocoTestReport exclui o gerado pelo Micronaut (*$Introspection*, *$Definition*, *$IntrospectionRef*, *$Intercepted*), o serializer do serde 3.x (Serde*) e as templates JTE precompiladas.

Fixtures de conformidade

Em src/test/resources/fixtures/<tipo>-YYYYMMDD/:

  • input.xlsx — a planilha do período (derivada de uma planilha real, anonimizada).
  • resultado-python.json — o golden (saída do fluxo Python) a bater.

Uma fixture por família de arquivo; o teste de conformidade roda a saída Java contra o golden. A receita de captura + o status por família estão no javadoc de processamento/planilha/ConformidadeTest.

Fixture nova nasce anonimizada

As planilhas de origem são reais (dado de cliente). Antes de commitar, troque nomes, CNPJs e valores sensíveis por sintéticos em lockstep no input.xlsx e no resultado-python.json (mesma substituição nos dois, preservando shape e o resultado esperado — a conformidade segue verde). "Repo privado" não é anonimização. Manter dado real exige justificativa declarada (REGRA Nº 3), não silenciosa.

Contrato do arquivo e detecção de tipo

O layout das planilhas e a ordem de detecção dos 10 tipos estão em a detecção automática de tipo da etapa 01 e na etapa 01. O contrato externo da API (envio/consulta) está em API REST.