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 nobuild.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 PKsbi_*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_HOMEapontar para uma JVM que o Gradle não reconhece, o build falha comIllegalArgumentException: <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-alpineem tmpfs), ligado ao contexto peloPostgresTestPropertyProvider. O teste que precisa de estado próprio (replicação lógica, flag do driver) sobe container dedicado. - Garage: o
GarageTestResourceda lib, com o bucketonpetro(garage.test.bucketna tasktest). Teste que não exercita storage fixaapp.storage.modo=PSQL. - Limpeza entre testes: o
AbstractRepositoryIntegrationTestapaga as tabelasbi_*/xls_*que os testes gravam, depois de esperar o processamento assíncrono pendente. Não é aIntegracaoComPostgresda lib porque oTRUNCATEde todas as tabelas apagaria o seed dabi_configuracao. - Travas: a task
testfalha 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.