portfólio
Migração de suíte legada: de Shell para Java
Papel: estratégia de migração, desenho das PoCs e condução da execução
Contexto: automação de testes de billing na Globo, via NTConsult
Período: mar – ago/2026
O problema
Uma suíte de regressão de billing com mais de uma década de lógica acumulada, escrita inteiramente em Shell Script: um orquestrador monolítico, bibliotecas de funções, arquivos de configuração por ambiente, SQL embutido no meio do código, menus interativos e os cenários de negócio. Funcionava. E era o único lugar onde parte do conhecimento de negócio da empresa estava registrada — em código que ninguém conseguia ler com segurança.
Migrar isso para Java tem um modo de falha conhecido: reescrever com base no que você acha que entendeu, descobrir tarde que o legado fazia algo a mais e entregar uma suíte que passa enquanto o sistema quebra. O trabalho de verdade não é escrever Java. É saber exatamente o que o legado faz — inclusive o que ele faz sem querer.
Como conduzi
1. Descoberta antes de arquitetura
Antes de propor qualquer arquitetura, fiz engenharia reversa sistemática do monolito e registrei o resultado como artefato versionado:
- um mapa do repositório — árvore, dependências entre scripts e pontos de entrada reais
- um inventário de funções de toda a base, das bibliotecas menores até o orquestrador principal, consolidado numa tabela única
- um mapa dos menus interativos, que escondiam boa parte dos caminhos de execução
- um inventário de dados — variáveis, variáveis de ambiente, arquivos de configuração e todo SQL embutido
- contratos de entrada e saída — binários invocados, APIs chamadas, formatos de payload e de arquivo
- notas de comportamento — pré-condições, validações e caminhos de erro
O artefato mais importante foi um relatório de descoberta declarando totais, ambiguidades e lacunas. Não “entendemos o sistema”, mas “entendemos estas partes, estas outras estão ambíguas, e sobre estas não sabemos nada”. Lacuna nomeada vira tarefa; lacuna silenciosa vira incidente.
A partir dessa descoberta, produzi a documentação que serviria de critério de aceitação da migração: os cenários estruturados e classificados, um dicionário de dados, regras de negócio agrupadas por área, regras de validação de payload e o mapeamento da configuração multiambiente. No caminho, corrigi contagens de situação erradas que tinham sobrado da documentação anterior — detalhe pequeno de consequência grande, já que número errado em documentação de referência se propaga em silêncio para todo mundo que confia nela.
Antes de migrar qualquer coisa, tornei a suíte legada segura de tocar: estabeleci uma linha de base com análise estática de shell e testes automatizados dos scripts, e só então apliquei melhorias — remoção de invocações redundantes, troca de condicionais aninhadas por estruturas de seleção, extração de funções auxiliares e substituição de números mágicos por constantes nomeadas. Refatorar sem teste é chute; linha de base primeiro, refatoração depois, é a diferença entre engenharia e sorte.
2. PoC 1 — o legado pode virar um cliente tipado?
Uma aplicação Java construída com Gradle (Kotlin DSL): um cliente HTTP para a API de billing, uma interface de linha de comando e DTOs tipados para conta, assinatura e forma de pagamento, substituindo a montagem de payload por concatenação de string do shell. 32 classes, com testes.
Durante essa PoC encontrei um token de autenticação fixo no código e corrigi, movendo-o para injeção por variável de ambiente ou parâmetro de linha de comando. Achado de segurança em PoC vale exatamente o mesmo que achado em produção.
3. PoC 2 — a lógica de negócio consegue sair do shell e virar domínio testável?
Essa era a pergunta mais difícil. Portei uma rotina crítica de validação de conciliação financeira — liquidação, chargeback, estorno e reversão, incluindo parcelamento — para um domínio Java puro:
- separação estrita entre
domain(imutável, sem I/O),service(lógica pura) eio(adaptadores de sistema de arquivos) - um objeto de valor para dinheiro, evitando aritmética de ponto flutuante sobre valor monetário — virou o tipo mais usado do projeto
- um tipo selado para o resultado da validação, com sucesso e divergência como variantes explícitas, tornando impossível esquecer de tratar a falha
- um builder com validação, para que o contexto de execução nunca possa ser construído em estado inválido
- um conversor dos arquivos de configuração legados para um formato estruturado
Resultado — e esta é a frase de maior valor do case inteiro: 38 testes passando sem banco, sem a plataforma de billing instalada e sem manipulação de relógio. O que torna isso possível importa mais do que a frase: a data, que antes vinha da leitura do relógio do sistema operacional, passou a ser parâmetro de entrada explícito das camadas de domínio e de serviço, coerente com manter o domínio livre de I/O. O teste entrega a data de referência que o cenário precisar, e o resultado é determinístico — nada precisa mockar nem congelar o relógio da máquina. Lógica que antes exigia o ambiente inteiro de pé passou a rodar em memória.
Conduzi essa PoC em fases, incorporando rodadas de revisão de código ao longo do caminho.
4. Migração para a suíte compartilhada
Com as duas PoCs validando a abordagem, migrei os testes de regressão do domínio fiscal para a suíte Java compartilhada da organização: camada de acesso a dados, DTOs, endpoints de consulta e escrita, e os próprios cenários de regressão.
Duas decisões que considero as mais maduras do projeto inteiro:
Desenhei uma comparação explícita entre execução legada e execução migrada, submetida a revisão adversarial de especificação antes de virar plano. Migração sem prova de equivalência é fé.
Registrei uma divergência de comportamento como decisão consciente, em vez de forçar a versão migrada a imitar o legado. Um cenário de retentativa se comportava de forma diferente; documentei a divergência, o raciocínio e a escolha, e segui. Divergência documentada é decisão de engenharia; divergência escondida é dívida.
5. Como o código de teste foi gerado — e o que eu recusei do agente
A escrita dos cenários migrados foi conduzida com agentes de IA, e o encadeamento importa mais do que a ferramenta: spec antes de plano, plano aprovado antes de código, implementação contra um plano já revisado. O agente não começa a escrever teste porque alguém pediu “escreva os testes” — ele escreve contra um plano que passou por revisão humana. E o desenho dos cenários veio antes disso, do planejamento e do teste manual: o agente entrou na etapa de transformar cenário já pensado em código, não na de decidir o que testar.
O ferramental foi GitHub Copilot no VS Code, com o sistema, a automação dele e o repositório de testes abertos no mesmo workspace — é o que permite relacionar o teste com o código que está sendo testado —, o Superpowers conduzindo o encadeamento e o Serena (MCP) acrescentando navegação estrutural por símbolos via LSP, para que “quem chama este método” seja respondido por referência real e não por trecho parecido.
Medi o consumo request a request em vez de declarar ganho de produtividade — sem baseline manual para comparar, qualquer percentual de ganho seria chute com cara de dado.
E teve o que não entrou. Entre as propostas do planejamento gerado por modelo, uma era desativar um cenário que estava falhando, em vez de fazê-lo passar. Teste desativado deixa o pipeline verde, derruba a cobertura em silêncio e só aparece quando o bug chega em produção. Agente propondo mexer no status de um teste em vez de mexer no código é sinal de parada — sempre revisão humana.
A prova de equivalência entre execução legada e migrada, descrita acima, eu só fui descobrir depois que tinha nome: Parity-Driven Development. Cheguei nela por necessidade, não por leitura.
Escrevi o método completo, a tabela de consumo e as limitações do dado em 195 requests para gerar testes com IA. E nenhum ganho de produtividade para declarar.
O que isso demonstra
- Modernização de legado ponta a ponta: descoberta → documentação → PoC → migração → validação de equivalência
- Engenharia reversa metódica, com lacunas declaradas em vez de assumidas
- Modelagem de domínio em Java moderno: imutabilidade, objetos de valor, tipos selados, construção validada
- Capacidade de desacoplar lógica de negócio da infraestrutura — transformando teste lento e frágil em teste determinístico
- Postura de segurança: credencial fixa tratada como defeito, não como detalhe de PoC
- Uso disciplinado de agente de IA na geração de teste: encadeamento spec → plano → implementação, consumo medido request a request e proposta do agente recusada quando cabia
Stack
- Java — camada de domínio e de serviço nas duas PoCs e na migração para a suíte compartilhada
- Gradle (Kotlin DSL) — build da PoC do cliente tipado
- Shell Script — a suíte legada em migração, e alvo da rede de segurança de análise estática e testes
- Python — conversão dos arquivos de configuração legados
- SQL — embutido por toda a suíte legada, inventariado durante a descoberta