Pruebas de integración de Go con Docker: de la teoría a la práctica avanzada
\n\nLas pruebas de integración son una etapa crítica en el desarrollo de microservicios en Go. Sin verificar la interacción con bases de datos, colas de mensajes y APIs externas, corres el riesgo de obtener "funciona en mi máquina" en su manifestación más desagradable. Docker permite levantar entornos limpios y aislados para cada prueba, y las técnicas avanzadas permiten hacerlo de forma rápida, paralela y sin dolor. En este artículo, analizaremos enfoques reales utilizados por ingenieros senior.
\n\n1. Por qué Docker es el socio ideal para las pruebas de integración en Go
\n\nGo se compila en un binario estático, lo que lo convierte en un candidato ideal para la contenerización. En las pruebas de integración, Docker resuelve tres tareas clave:
\n- \n
- Aislamiento: cada prueba obtiene una base de datos o Redis fresca sin "basura" de ejecuciones anteriores. \n
- Reproducibilidad: las pruebas funcionan igual en la máquina del desarrollador, en CI/CD y en producción. \n
- Velocidad: los contenedores correctamente configurados se inician en segundos, no en minutos. \n
El error principal de los principiantes es ejecutar un solo contenedor para todas las pruebas. Esto provoca condiciones de carrera y errores "intermitentes". El enfoque avanzado: un contenedor por paquete de prueba o incluso por prueba individual.
\n\n2. Testcontainers para Go: contenedores gestionados desde el código
\n\nLa biblioteca testcontainers-go permite gestionar contenedores Docker directamente desde las pruebas de Go. Esto es más elegante que escribir scripts bash o depender de un Docker Compose previamente ejecutado.
Instalación:
\ngo get github.com/testcontainers/testcontainers-go\ngo get github.com/testcontainers/testcontainers-go/modules/postgres\n\nEjemplo: probar PostgreSQL con migraciones:
\npackage integration_test\n\nimport (\n "context"\n "database/sql"\n "testing"\n "time"\n\n "github.com/testcontainers/testcontainers-go"\n "github.com/testcontainers/testcontainers-go/modules/postgres"\n "github.com/testcontainers/testcontainers-go/wait"\n _ "github.com/lib/pq"\n)\n\nfunc TestUserRepository(t *testing.T) {\n ctx := context.Background()\n\n pgContainer, err := postgres.RunContainer(ctx,\n testcontainers.WithImage("postgres:16-alpine"),\n postgres.WithDatabase("testdb"),\n postgres.WithUsername("user"),\n postgres.WithPassword("password"),\n testcontainers.WithWaitStrategy(\n wait.ForLog("database system is ready to accept connections").\n WithOccurrence(2).\n WithStartupTimeout(30*time.Second),\n ),\n )\n if err != nil {\n t.Fatal(err)\n }\n defer pgContainer.Terminate(ctx)\n\n connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")\n if err != nil {\n t.Fatal(err)\n }\n\n db, err := sql.Open("postgres", connStr)\n if err != nil {\n t.Fatal(err)\n }\n defer db.Close()\n\n // Aquí ejecutamos migraciones y pruebas\n _, err = db.Exec(`CREATE TABLE IF NOT EXISTS users (\n id SERIAL PRIMARY KEY,\n name TEXT NOT NULL\n )`)\n if err != nil {\n t.Fatal(err)\n }\n\n // Probamos el repositorio\n repo := NewUserRepository(db)\n user, err := repo.Create(ctx, "Alice")\n if err != nil {\n t.Fatal(err)\n }\n if user.Name != "Alice" {\n t.Errorf("expected Alice, got %s", user.Name)\n }\n}\n\nPuntos clave:
\n- \n
WithWaitStrategy— esperamos hasta que PostgreSQL esté realmente listo, no solo que el contenedor esté iniciado. \n defer pgContainer.Terminate(ctx)— garantiza la limpieza incluso en caso de pánico. \n - Cada prueba obtiene su propia instancia de base de datos — aislamiento completo. \n
3. Pruebas paralelas y gestión de recursos
\n\nGo soporta la paralel