tsma – Linea de defensa contra regresiones en codigo legado

Como se refactoriza codigo sin tests?

Heredaste un codigo legado de 100.000 lineas. No hay tests. Quieres refactorizar, pero tocar cualquier cosa podria romper algo. Escribir tests requiere entender el codigo, y entender el codigo requiere documentacion – que tampoco existe.

Nadie lo toca. Se pudre mas.

Todo el codigo legado del mundo esta atrapado en este punto muerto. Entre el 60 y el 80% del presupuesto de TI de las empresas Fortune 500 se destina al mantenimiento de sistemas legados. El 42% del tiempo de los desarrolladores se consume en deuda tecnica.

Y si un LLM pudiera escribir los tests por ti?


Los problemas cuando delegas los tests a un LLM

Pide a un LLM que “escriba tests para esta funcion” y algo produce. Los problemas son tres.

Primero, no sabe por donde empezar. Cuando hay 527 funciones, empiezas por la numero 1 en orden? Por la mas critica? No hay criterio.

Segundo, no puedes verificar la calidad de los tests. Los tests del LLM pasan. Pero estan verificando realmente el comportamiento de la funcion, o son cascarones vacios que solo llaman a la funcion sin ningun assert? Tendrias que leer cada uno manualmente para saberlo.

Tercero, sin retroalimentacion, los tests del LLM se estancan en el 60-70%. Solo con “testea esta funcion” no se alcanza el 100% de cobertura de ramas. Hay que indicarle que ramas faltan para que complete el resto.

No es que los LLM no sepan escribir tests. El problema es la ausencia de una estructura que le diga al LLM que escribir y que tan bien lo escribio.


tsma: Un rail de tests impulsado por un solo comando

tsma es una herramienta CLI que indexa todas las funciones de un proyecto, detecta la presencia de tests, mide la cobertura y proporciona retroalimentacion precisa a agentes LLM.

El agente necesita conocer un solo comando:

$ tsma next

Este unico comando impulsa todo el ciclo:

$ tsma next          # Muestra la siguiente funcion sin test
  → Escribe un test
$ tsma next          # Detecta el nuevo test, lo ejecuta, mide cobertura
  → 100%? PASS, siguiente funcion
  → <100%? Reporta las ramas no cubiertas con numeros de linea
$ tsma next          # Re-mide el test revisado
  → Mejore o no, lo marca como DONE y continua

Repite hasta que aparezca “All functions complete!”.


Validado en 527 funciones

tsma se aplico a un proyecto Go real con 527 funciones.

ResultadoCantidadProporcion
PASS (100% cobertura de ramas)24646,7%
DONE (best-effort)28153,3%
TODO (sin procesar)00%

246 funciones alcanzaron el 100% de cobertura de ramas. Las 281 restantes no llegaron al 100%, pero se escribieron tests hasta donde fue posible.

Por que algunas funciones no pueden alcanzar el 100%?


Funciones que alcanzan el 100% y las que no

Que una funcion pueda alcanzar el 100% de cobertura de ramas depende de como recibe sus dependencias.

Interfaz (mockable) – 100% alcanzable:

type Handler struct {
    svc AuthSvc              // interface -- reemplazable con mock
}

Inyecta un mock en los tests y puedes controlar todas las rutas:

svc := mocks.NewMockAuthSvc(ctrl)
svc.EXPECT().Login(...).Return(result, nil)   // ruta de exito
svc.EXPECT().Login(...).Return(nil, err)      // ruta de fallo

Tipo concreto (not mockable) – 100% imposible:

type Handler struct {
    svc *service.SMSImportService    // puntero a struct -- no reemplazable
}

La implementacion real se ejecuta con dependencias internas de bases de datos, APIs externas, etc. No puedes forzar errores especificos ni valores de retorno especificos. Las ramas que dependen de esos resultados son inalcanzables en tests unitarios.

La respuesta de tsma: Tras la retroalimentacion de ramas no cubiertas, intenta una vez mas. Si siguen siendo inalcanzables, acepta DONE. Esto no es una limitacion de la herramienta – refleja la testeabilidad del codigo. Introducir interfaces (DI) haria posible el 100%, pero eso implica modificar el codigo original.


La retroalimentacion transforma radicalmente los tests del LLM

El valor central de tsma no es la indexacion ni la medicion de cobertura. Es indicarle al LLM exactamente que ramas no estan cubiertas, con numero de linea.

Sin retroalimentacion:

"Escribe tests para la funcion ListContracts"
→ El LLM solo testea el happy path
→ Cobertura 60-70%

Con retroalimentacion:

"Escribe tests para la funcion ListContracts"
→ Cobertura 65% (11/17)
→ UNCOVERED:
    line 41 -- if params.Status != nil
    line 44 -- if params.BuildingId != nil
    line 70 -- if err != nil (CountSummary)
→ El LLM agrega tests que cubren exactamente esas ramas
→ Cobertura 100%

El mismo LLM. La unica diferencia es la presencia de retroalimentacion. Tres lineas de numeros de linea separan el 60% del 100%.


El progreso sobrevive aunque el agente muera

Los agentes LLM se caen. Limites de tokens, errores de red, sesiones cortadas. No puedes procesar 527 funciones en una sola sesion.

tsma persiste el progreso en .tsma/session.json.

$ tsma status

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

El agente muere en la funcion 200? Un nuevo agente ejecuta tsma next y continua desde la 201. session.json es el checkpoint.

Multiples agentes pueden turnarse sin conflictos. Cada funcion es atomica.


La sesion es cache; los archivos fuente son la verdad

Uno de los principios de diseno de tsma: la sesion es cache y los archivos fuente son el source of truth.

Si borras un archivo de test, aunque session.json lo registre como PASS, esa funcion vuelve a TODO. La sesion nunca se desincroniza de la realidad.

Principio:
  Aunque session.json diga "PASS"
  Si el archivo de test no existe → TODO
  Si el archivo fuente cambio → objetivo de re-medicion

Instrucciones para el agente LLM

El agente necesita exactamente 6 lineas de instrucciones:

1. Ejecutar tsma next
2. Si es TODO -- leer la funcion y escribir un test
3. Si el test falla -- leer el error y corregir el test
4. Si se muestran ramas no cubiertas -- agregar tests que cubran esas ramas
5. Si es PASS/DONE -- la siguiente funcion se muestra automaticamente
6. Repetir hasta que aparezca "All functions complete!"

El unico comando que el agente necesita conocer es tsma next. El CLI restringe el resto.


Trenes y vias

Vibe coding es un tren. Es rapido. Pero sin vias, descarrila.

Todas las herramientas de AI coding se enfocan en hacer el tren mas rapido. Modelos mas grandes, agentes mas inteligentes, mejores prompts. Pero cuanto mas rapido va el tren, peor es el descarrilamiento.

tsma es la via. El LLM genera tests (Neural), y el CLI define “hasta aqui y no mas” (Symbolic Constraint). La creatividad del LLM se mantiene intacta, pero la calidad de los resultados la impone la maquina.

Antestsma
Escritura de testsHumano (lento) o LLM (caotico)LLM escribe, CLI verifica
Por donde empezar?Decision humanaCLI determina el orden
Control de calidadRevision humanaCLI mide cobertura
RetroalimentacionNingunaNumeros de linea de ramas no cubiertas
Seguimiento de progresoNingunosession.json automatico

El LLM genera libremente. Pero corre solo sobre la via llamada tsma next.


Soporte de lenguajes

LenguajeIndexadorTest RunnerCobertura
Gogo/astgo testgo test -coverprofile
TypeScriptregexnpx vitest / npx jestc8 / istanbul
Pythonregexpytestcoverage.py

Go usa un parser AST para la extraccion precisa de funciones. TypeScript y Python usan extraccion basada en regex.

Los archivos generados (*_gen.go, *.pb.go), archivos de test y vendor/node_modules se excluyen automaticamente de la indexacion.


Instalacion y uso

make install
cd your-legacy-project
tsma next

Eso es todo.

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