tsma — A linha de defesa contra regressão em código legado

Como refatorar código sem testes?

Você herdou cem mil linhas de código legado. Sem testes. Quer refatorar, mas não sabe o que vai quebrar se mexer. Para escrever testes, precisa entender o código; para entender o código, precisa de documentação — que também não existe.

Ninguém mexe. Apodrece ainda mais.

Todo código legado do mundo está preso nesse impasse. Empresas da Fortune 500 gastam entre 60% e 80% do orçamento de TI na manutenção de sistemas legados. 42% do tempo dos desenvolvedores é consumido lidando com dívida técnica.

E se um LLM pudesse escrever os testes por você?


Os problemas de delegar testes ao LLM

Quando você pede ao LLM “escreva um teste para esta função”, algo sai. O problema é triplo:

Primeiro, ele não sabe por onde começar. Com 527 funções — começa pela primeira em ordem? Pela mais importante? Não há critério.

Segundo, não é possível verificar a qualidade do teste. O LLM escreveu um teste e ele passou. Mas esse teste realmente verifica o comportamento da função, ou é apenas uma chamada sem assert — uma casca vazia? Só lendo manualmente para saber.

Terceiro, sem feedback os testes do LLM param em 60–70%. Apenas “teste esta função” não alcança 100% de cobertura de branches. É preciso informar quais branches estão faltando para que ele complete.

O LLM não é incapaz de escrever testes. O problema é a ausência de uma estrutura que diga o que escrever e quão bem foi escrito.


tsma: um trilho de testes que funciona com um comando

tsma é uma ferramenta CLI que indexa todas as funções do projeto, detecta a presença de testes, mede cobertura e fornece feedback preciso para agentes LLM.

O único comando que o agente precisa conhecer:

$ tsma next

Esse único comando aciona o loop inteiro:

$ tsma next          # mostra a próxima função sem teste
  → você escreve o teste
$ tsma next          # detecta o novo teste, executa, mede cobertura
  → 100%? PASS, avança para a próxima função
  → <100%? mostra branches não cobertas com números de linha
$ tsma next          # remede o teste corrigido
  → melhorou ou não, marca como DONE e avança

Repete até aparecer “All functions complete!”.


Validado em 527 funções

tsma foi aplicado a um projeto Go real (527 funções).

ResultadoQtdProporção
PASS (100% cobertura de branches)24646,7%
DONE (melhor esforço)28153,3%
TODO (não processado)00%

246 funções alcançaram 100% de cobertura de branches. As 281 restantes não chegaram a 100%, mas tiveram testes escritos até o limite possível.

Por que algumas funções não alcançam 100%?


Funções que alcançam 100% e funções que não alcançam

Se uma função pode alcançar 100% de cobertura de branches depende de como ela recebe suas dependências.

Interface (mockable) — 100% alcançável:

type Handler struct {
    svc AuthSvc              // interface — substituível por mock
}

Injetando um mock no teste, você controla todos os caminhos:

svc := mocks.NewMockAuthSvc(ctrl)
svc.EXPECT().Login(...).Return(result, nil)   // caminho de sucesso
svc.EXPECT().Login(...).Return(nil, err)      // caminho de erro

Tipo concreto (not mockable) — 100% impossível:

type Handler struct {
    svc *service.SMSImportService    // ponteiro de struct — não substituível
}

A implementação real roda com dependências internas como banco de dados e APIs externas. Não é possível provocar um erro específico ou forçar um resultado específico. Branches que dependem desses resultados são inalcançáveis por testes unitários.

Resposta do tsma: após feedback sobre branches não cobertas, tenta mais uma vez. Se ainda não alcançar, aceita como DONE. Isso não é limitação da ferramenta, mas reflexo da testabilidade do código. Introduzir interfaces (DI) torna 100% possível, mas isso significa modificar o código original.


Feedback transforma drasticamente os testes do LLM

O valor central do tsma não é a indexação nem a medição de cobertura. É apontar com precisão as branches não cobertas, com números de linha.

Sem feedback:

"Escreva um teste para a função ListContracts"
→ LLM testa apenas o happy path
→ cobertura 60–70%

Com feedback:

"Escreva um teste para a função ListContracts"
→ cobertura 65% (11/17)
→ UNCOVERED:
    line 41 — if params.Status != nil
    line 44 — if params.BuildingId != nil
    line 70 — if err != nil (CountSummary)
→ LLM adiciona testes que cobrem exatamente essas branches
→ cobertura 100%

Mesmo LLM. A única diferença é a presença do feedback. Três linhas com números de linha separam 60% de 100%.


Mesmo que o agente morra, o progresso é preservado

Agentes LLM caem. Limite de tokens, erro de rede, sessão desconectada. Não é possível processar 527 funções em uma única sessão.

tsma persiste o estado de progresso em .tsma/session.json.

$ tsma status

527 functions
PASS:  246 (46.7%)
DONE:  281 (53.3%)
TODO:    0 (0.0%)

Se o agente morre na função 200? Um novo agente executa tsma next e continua da 201. session.json é o checkpoint.

Vários agentes podem revezar sem conflito. A operação é atômica no nível da função.


A sessão é cache, os arquivos-fonte são a verdade

Um dos princípios de design do tsma: session é cache e os arquivos-fonte são o source of truth.

Se você apagar um arquivo de teste, mesmo que session.json registre PASS, a função volta a ser TODO. A sessão não se descola da realidade.

Princípio:
  mesmo que session.json diga "PASS"
  se não houver arquivo de teste → TODO
  se o arquivo-fonte mudou → remesurar

Instruções para o agente LLM

O agente precisa de apenas 6 linhas:

1. Execute tsma next
2. Se TODO — leia a função e escreva o teste
3. Se o teste falhar — leia o erro e corrija o teste
4. Se branches não cobertas aparecerem — adicione testes que as cubram
5. Se PASS/DONE — a próxima função aparece automaticamente
6. Repita até aparecer "All functions complete!"

O único comando que o agente precisa é tsma next. O resto é imposto pela CLI.


O trem e os trilhos

Vibe coding é um trem. Rápido. Mas sem trilhos, descarrila.

Todas as ferramentas de codificação com IA estão focadas em tornar o trem mais rápido. Modelos maiores, agentes mais inteligentes, prompts melhores. Mas quanto mais rápido o trem, maior o estrago do descarrilamento.

tsma são os trilhos. O LLM gera os testes (Neural), e a CLI define “até aqui” (Symbolic Constraint). A criatividade do LLM permanece livre, mas a qualidade dos resultados é imposta pela máquina.

Convencionaltsma
Escrita de testesHumano (lento) ou LLM (caótico)LLM escreve, CLI verifica
Por onde começar?Humano decideCLI determina a ordem
Verificação de qualidadeHumano revisaCLI mede cobertura
FeedbackNenhumNúmeros de linha de branches não cobertas
Rastreamento de progressoNenhumsession.json automático

O LLM gera livremente. Mas corre apenas sobre os trilhos do tsma next.


Linguagens suportadas

LinguagemIndexadorRunner de testesCobertura
Gogo/astgo testgo test -coverprofile
TypeScriptregexnpx vitest / npx jestc8 / istanbul
Pythonregexpytestcoverage.py

Go usa parser AST para extração precisa de funções. TypeScript e Python são baseados em expressões regulares.

Arquivos gerados (*_gen.go, *.pb.go), arquivos de teste e vendor/node_modules são automaticamente excluídos da indexação.


Instalação e execução

make install
cd your-legacy-project
tsma next

Isso é tudo.

MIT License. github.com/park-jun-woo/tsma