Pruebas de regresión en PostgreSQL con Spawn y golden files
En pocas palabras: Podés armar pruebas de regresión en PostgreSQL con Spawn, el sistema de builds SQL de Mark Saward: cuatro comandos (new, run, expect, compare) ejecutan tus consultas vía psql, capturan la salida y la comparan contra golden files, sin instalar extensiones en la base.
El desarrollador Mark Saward publicó en dev.to una guía para armar pruebas de regresión postgresql con Spawn, su sistema de builds SQL: capturás la salida de psql, la comparás contra un archivo de referencia y cualquier cambio inesperado en tu base salta a la vista.
Las pruebas de regresión en PostgreSQL son tests que verifican que un cambio en el esquema, en una función o en un trigger no rompa comportamientos que ya funcionaban. Spawn es un sistema de builds SQL con soporte de migraciones y testing por golden files: ejecuta tus consultas a través de psql, captura stdout y stderr, y los compara contra una salida esperada. No exige instalar extensiones en la base.
En 30 segundos
- La novedad: Saward publicó un tutorial completo de testing con Spawn, la herramienta que él mismo desarrolló.
- El flujo: cuatro comandos (new, run, expect, compare) cubren todo el ciclo de un test con golden files.
- Los requisitos: solo la CLI de Spawn y una conexión psql; nada de extensiones instaladas en el servidor.
- El aislamiento: cada test corre sobre una copia creada con CREATE DATABASE … TEMPLATE, así el estado inicial siempre es idéntico.
- El extra: macros con plantillas Inja y fixtures en JSON para reutilizar datos de prueba entre tests.
¿Qué son las pruebas de regresión en PostgreSQL?
Una prueba de regresión en una base de datos verifica que, después de un cambio (una migración, una función nueva, un índice agregado), todo lo que ya funcionaba siga funcionando igual. La diferencia con el testing unitario está en el alcance: el test unitario mira una pieza aislada, mientras que la regresión recorre flujos completos donde intervienen triggers, restricciones y estado acumulado entre pasos.
Ponele que modificás una función de órdenes y sin querer le metés un stock negativo. Si solo testeás la función en frío, quizá no lo veas. Si tenés una regresión que crea una orden, actualiza ítems y borra otros, el CHECK constraint te lo grita en la cara. Esa es la diferencia real entre los dos niveles.
¿Qué herramientas hay para testear PostgreSQL y cuándo conviene cada una?
Existen tres familias principales: extensiones dentro de la base (pgTAP), el driver oficial de regresión del motor (pg_regress) y herramientas externas que orquestan desde la CLI, como Spawn o Testcontainers. La elección depende de dónde quieras que vivan tus tests y qué comportamiento necesites observar.
| Herramienta | Dónde corre | Fortaleza principal |
|---|---|---|
| Spawn | CLI externa, vía psql | Regresión de flujos completos con golden files, sin extensiones |
| pgTAP | Extensión dentro de la base | Aserciones unitarias de funciones, esquema y permisos |
| pg_regress | Suite oficial del motor | Tests del núcleo de PostgreSQL y extensiones nativas |
| Testcontainers | Contenedores Docker desde tu lenguaje | Tests de integración de la app contra una base real efímera |

pgTAP es gratuito y open source (pgtap.org) y lleva años siendo el estándar para aserciones dentro de la base. pg_regress está documentado en el sitio oficial de PostgreSQL (regress.html) y es lo que usa el propio proyecto para testear el motor. Spawn apuesta por otra cosa: que el test sea texto plano que entra y sale por psql. Ojo con esto: a agosto de 2026, Spawn solo soporta conexión vía psql, así que si tu flujo depende de otro cliente, no zafa.
Para seguir el tutorial alcanzó con la CLI de Spawn y un docker compose con PostgreSQL levantado en la máquina del autor. Tema relacionado: el éxito de Omarchy en GitHub.
¿Cómo funciona el golden file testing con Spawn?
El golden file testing compara la salida real de tus consultas contra una salida guardada como referencia. Spawn ejecuta el SQL a través de psql, captura stdout y stderr completos, y los diffea contra el archivo esperado; si aparece una sola línea distinta, el test falla mostrando exactamente qué cambió (spoiler: la “magia de hechicero” que promete el título es un diff de texto bien implementado).
El ciclo completo es corto: creás el test con spawn test new <nombre> y lo ejecutás con run para ver qué devuelve. Si querés inspeccionar el SQL exacto que se envía a través de psql, le pegás una mirada a build. Cuando la salida te convence, la fijás como referencia con expect, y desde ese momento cada compare te tira un diff de lo que se movió. Cuatro comandos, ni uno más.
- spawn test new: genera el esqueleto del test dentro de tests/nombre/test.sql, listo para editar.
- spawn test run: ejecuta el SQL vía psql y muestra la salida cruda, ideal para iterar rápido.
- spawn test build: revela el SQL final que se enviará a psql, clave cuando usás macros.
- spawn test expect: guarda la salida actual como archivo esperado, el punto de partida de toda regresión.
- spawn test compare: vuelve a correr todo y diffea; ante cambios muestra un reporte [FAIL] con las líneas en conflicto.
En el ejemplo de la guía, el primer test es un SELECT trivial sobre una tabla de órdenes. Cambian una columna, corren compare, y el diff marca la discrepancia línea por línea. Simple, casi aburrido. Acá viene lo bueno: ese mismo mecanismo escala a flujos enteros con triggers y errores esperados, que es donde se pone interesante.
¿Cómo crear copias limpias de la base de datos para cada test?
La forma más confiable de aislar un test es clonar la base plantilla con CREATE DATABASE … TEMPLATE, trabajar sobre la copia y eliminarla al terminar. Cada corrida parte del mismo estado exacto, sin importar cuántas veces ejecutes la suite.
¿Por qué no alcanza con envolver todo en una transacción y hacer rollback? Porque hay operaciones que no pueden correr dentro de un bloque transaccional (crear bases de datos, por ejemplo) y porque a veces querés validar comportamiento entre commits, no solo dentro de uno. El patrón que muestra Saward cubre esos casos: primero elimina la base de test si quedó de una corrida fallida, después la crea desde la plantilla, se conecta con \c, ejecuta las inserciones, vuelve a la base postgres y borra la copia. En cómo evitar caídas de servicio profundizamos sobre esto.
Eso sí: el clonado con TEMPLATE falla si hay conexiones activas sobre la base origen. Si dejaste una sesión psql abierta, cerrala o reconectá a postgres antes de lanzar el test. Detalle chico, dolor grande a las tres de la mañana.
¿Cómo se testean funciones y triggers en PostgreSQL?
Con Spawn podés validar funciones y triggers incluyendo sus errores esperados como parte de la salida. La guía lo demuestra con una función create_order que recibe un tipo compuesto de ítems y un trigger llamado order_item_quantity_trigger que ajusta el stock de la tabla item ante cada cambio en order_item (altas, ediciones o borrados).
El script de test cuenta una historia completa: crea una orden con una manzana y dos bananas (las manzanas quedan en cero), suma una banana más (el stock baja a dos), borra las bananas de la orden (vuelven a cinco) y finalmente intenta otra orden que debería reventar por falta de manzanas. Y revienta: la salida captura el ERROR del CHECK constraint item_quantity_on_hand_check tal cual lo escupe psql.
Dos detalles técnicos hacen posible esto. Primero, Spawn corta ante el primer error por defecto, así que el test desactiva ese comportamiento para que el fallo esperado quede registrado en vez de matar la corrida. Segundo, los mensajes de error traen timestamps que cambian en cada ejecución; con verbosity terse se obtienen mensajes estables, y con gset se capturan valores dinámicos (como el ID de la orden creada) en variables reutilizables. La documentación de Spawn dedica una sección entera al no determinismo en tests.
Otra buena práctica del tutorial: comentarios dentro del test explicando qué valida cada bloque. El próximo que mantenga esa suite (probablemente vos, dentro de seis meses) te lo va a agradecer.
¿Cómo generar datos de prueba reutilizables con fixtures y macros?
Spawn usa plantillas Inja para macros y lee archivos JSON como fixtures, así definís un dataset una vez y lo reutilizás en todos los tests que lo necesiten. Se acabó copiar y pegar INSERTs entre scripts. Cubrimos ese tema en detalle en integrar los tests en tu pipeline.
El ejemplo arma un macro create-item.sql que genera el INSERT con valores por defecto para todo salvo el nombre. Hay un detalle fino: para la primary key usa item_id=”default” pasado por el filtro safe, porque Spawn escapa cualquier valor de entrada como literal por seguridad, y el filtro le indica que muestre ese token tal cual, sin comillas. Después viene el fixture items.json con cuatro productos y un segundo macro que lo recorre llamando al primero por cada elemento. Al construir con spawn test build se verifica que hasta el apóstrofe de un nombre quede escapado correctamente.
¿Y si un test necesita menos datos que otro? Sin problema: incluís el fixture donde aplica y lo omitís donde no. Cada suite decide qué cargar.
Errores comunes al armar pruebas de regresión en PostgreSQL
Viendo el tutorial y años de suites rotas, estos son los tropiezos más frecuentes:
- Resolver todo con transacciones y rollback: funciona en muchos casos, pero CREATE DATABASE no puede ir dentro de un bloque transaccional y ciertos comportamientos solo se observan entre commits. Para eso existe el clonado con TEMPLATE.
- Dejar conexiones abiertas a la base plantilla: el clonado falla si hay sesiones activas sobre el origen. Reconectá a postgres antes de correr la suite.
- Asumir que los errores cortan el test: Spawn aborta ante el primer error por defecto. Si tu caso de prueba valida un error esperado, ajustá ese comportamiento o jamás llegás a la aserción.
- Ignorar la salida no determinista: timestamps, IDs generados y la verbosidad por defecto rompen el golden file en cada corrida. Usá verbosity terse y gset para estabilizar la salida.
Preguntas Frecuentes
¿Es pgTAP gratuito?
Sí. pgTAP es una extensión open source de PostgreSQL que se instala en la base y expone funciones de aserción estilo TAP. No tiene planes ni licencias de pago; la diferencia con Spawn es que requiere instalar la extensión en cada base donde quieras testear.
¿Qué es Spawn y necesita extensiones en PostgreSQL?
Spawn es un sistema de builds SQL con soporte de migraciones y testing por golden files, desarrollado por Mark Saward. No requiere instalar ninguna extensión: alcanza con la CLI y una conexión psql a la base, ya que toda la lógica de captura y comparación corre fuera del servidor. Esto se conecta con lo que analizamos en levantar entornos de prueba en Mac.
¿Puedo testear con Spawn si mis migraciones no pasan por Spawn?
El autor diseñó la herramienta alrededor de su flujo de migraciones y recomienda usarlo así, pero el propio tutorial arranca creando la base manualmente con psql sobre docker compose. En la práctica, mientras tengas una base plantilla con el estado inicial correcto, el testing funciona igual.
¿Cómo integro estos tests en un pipeline de CI/CD?
Como todo corre por CLI y psql, cualquier runner que tenga ambos binarios puede ejecutar spawn test compare y marcar el pipeline como fallido si aparecen diffs. La guía no incluye configuraciones específicas de GitHub Actions ni GitLab, así que ese armado queda para cada equipo.
¿Cuál es la diferencia entre prueba unitaria y de regresión en bases de datos?
La prueba unitaria valida una pieza aislada: una función, una vista, una consulta puntual. La de regresión valida flujos completos con estado acumulado: inserciones que disparan triggers y CHECK constraints que rechazan datos entre pasos de un mismo escenario.
Conclusión
La guía deja dos cosas claras. Primero, testear una base de PostgreSQL a fondo no requiere infraestructura exótica: una CLI, psql y disciplina con los golden files alcanzan para validar triggers y errores esperados. Segundo, el aislamiento por clonado de plantillas resuelve el problema eterno del estado sucio entre corridas.
Mi lectura después de años viendo suites de base abandonadas: el enfoque de golden files brilla para regresión de flujos completos, porque el diff te señala la línea exacta que cambió. Para lógica pura y aserciones granulares, pgTAP sigue siendo mi caballo de batalla. Si ya usás Spawn para migrar, probale el testing hoy mismo; si no, el tutorial igual vale la pena como clase magistral de diseño de tests de base.
Fuentes
- Powerful regression tests for your PostgreSQL project – Guía original de Mark Saward en dev.to
- Documentación oficial de Spawn – testing, migraciones y manejo del no determinismo
- pgTAP – Framework de testing unitario para PostgreSQL
- Regression Testing – Documentación oficial de PostgreSQL sobre pg_regress






