Pruebas de Integración de Go con Docker: Nivel Avanzado
Las pruebas de integración son una etapa críticamente importante en el desarrollo de microservicios robustos en Go. Las pruebas unitarias aisladas no garantizan que tu aplicación interactúe correctamente con dependencias externas: bases de datos, colas de mensajes, cachés y APIs de terceros. Docker permite levantar instancias reales de estos servicios en el entorno de prueba, asegurando la máxima fiabilidad de las verificaciones. En este artículo, analizaremos técnicas avanzadas de pruebas de integración de aplicaciones Go utilizando Docker.
¿Por qué Docker es importante para las pruebas de integración en Go?
El enfoque tradicional con mocks y stubs verifica solo la lógica de tu código, pero no el comportamiento en conjunto con un SGBD o broker real. Docker resuelve este problema:
- Reproducibilidad del entorno: las pruebas se ejecutan en condiciones idénticas localmente y en CI/CD.
- Aislamiento: cada conjunto de pruebas puede tener su propia instancia del servicio.
- Sin "magia": pruebas exactamente el driver/cliente que usas en producción.
Sin embargo, la gestión manual de contenedores mediante os/exec o scripts de shell rápidamente se vuelve inestable. Aquí es donde entran en juego bibliotecas como testcontainers-go.
Biblioteca Testcontainers para Go: instalación y ejemplo básico
testcontainers-go es un wrapper de Go que gestiona programáticamente el ciclo de vida de los contenedores Docker. Descarga automáticamente imágenes, las inicia, espera a que estén listas y limpia los contenedores después de la prueba.
Instalación
go get github.com/testcontainers/testcontainers-gogo get github.com/testcontainers/testcontainers-go/modules/postgresEjemplo básico: pruebas con PostgreSQL
package integration
import ( "context" "database/sql" "testing" "time"
"github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/postgres" "github.com/testcontainers/testcontainers-go/wait" _ "github.com/lib/pq")
func TestPostgresConnection(t *testing.T) { ctx := context.Background()
// 1. Iniciamos el contenedor PostgreSQL pgContainer, err := postgres.RunContainer(ctx, testcontainers.WithImage("postgres:15"), postgres.WithDatabase("testdb"), postgres.WithUsername("user"), postgres.WithPassword("password"), testcontainers.WithWaitStrategy( wait.ForLog("database system is ready to accept connections"). WithOccurrence(2). WithStartupTimeout(30*time.Second)), ) if err != nil { t.Fatal(err) } defer pgContainer.Terminate(ctx) // Limpieza automática
// 2. Obtenemos la cadena de conexión connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable") if err != nil { t.Fatal(err) }
// 3. Nos conectamos y ejecutamos una consulta db, err := sql.Open("postgres", connStr) if err != nil { t.Fatal(err) } defer db.Close()
if err := db.Ping(); err != nil { t.Fatal("No se pudo conectar a la BD:", err) }
// Verificamos que podemos crear una tabla e insertar datos _, err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT)`) if err != nil { t.Fatal(err) } t.Log("¡Prueba de integración con PostgreSQL superada con éxito!")}Técnicas avanzadas: ejecución paralela y esperas personalizadas
Cuando hay muchas pruebas, la ejecución secuencial de contenedores para cada prueba ralentiza la ejecución. Usamos el paralelismo de Go y contenedores compartidos.
Pruebas paralelas con un contenedor compartido
Creamos un contenedor para todo el paquete mediante TestMain:
package integrat