--- title: "Cómo probar módulos de Terraform con Terratest en Ubuntu" description: "Instala Go y Terraform en Ubuntu 24.04, escribe tests en Go con Terratest que despliegan y validan un módulo de Terraform y ejecútalos en GitHub Actions." category: "Automatización e IaC" language: es source: https://cubepath.com/es/docs/automatización-e-iac/pruebas-de-infraestructura-con-terratest updated: 2026-09-25 --- # Cómo probar módulos de Terraform con Terratest en Ubuntu Terratest es una biblioteca de Go de Gruntwork para probar código de infraestructura de verdad: el test ejecuta `terraform init` y `terraform apply`, comprueba el resultado y destruye los recursos al terminar. En este tutorial instalarás Go y Terraform en Ubuntu 24.04, crearás un pequeño módulo de Terraform, escribirás un test con Terratest que lo despliega y valida sus salidas, y lo ejecutarás en un pipeline de GitHub Actions. El módulo de ejemplo usa el proveedor `local` de HashiCorp, que crea archivos en disco, así que puedes seguir la guía sin credenciales de ningún proveedor cloud. El mismo patrón sirve después para módulos que crean servidores, redes o registros DNS reales. ## Requisitos previos - Un servidor o equipo con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. - Un usuario no root con privilegios `sudo`. - `git` y `curl` instalados (`sudo apt install git curl`). - Conocimientos básicos de Terraform (recursos, variables y salidas). ## Paso 1: Instalar Go Terratest sigue las versiones recientes de Go, y el paquete `golang-go` de Ubuntu 24.04 (Go 1.22) suele quedarse corto. Instala la última versión estable desde go.dev. Primero consulta cuál es: ```bash GO_VERSION=$(curl -fsSL 'https://go.dev/VERSION?m=text' | head -n1) echo "$GO_VERSION" ``` ```output go1.27.1 ``` Descarga el archivo para tu arquitectura (`amd64` en la mayoría de servidores, `arm64` en ARM) y descomprímelo en `/usr/local`: ```bash curl -fsSLO "https://go.dev/dl/${GO_VERSION}.linux-amd64.tar.gz" sudo rm -rf /usr/local/go sudo tar -C /usr/local -xzf "${GO_VERSION}.linux-amd64.tar.gz" ``` Añade Go al `PATH` de tu usuario y recarga el perfil: ```bash echo 'export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin' >> ~/.profile source ~/.profile ``` Comprueba la instalación: ```bash go version ``` ```output go version go1.27.1 linux/amd64 ``` ## Paso 2: Instalar Terraform Instala Terraform desde el repositorio oficial de HashiCorp. Descarga la clave de firma en `/etc/apt/keyrings`: ```bash sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/hashicorp.gpg ``` Añade el repositorio e instala el paquete: ```bash echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list sudo apt update sudo apt install terraform ``` Verifica la versión: ```bash terraform version ``` ```output Terraform v1.16.4 on linux_amd64 ``` ## Paso 3: Crear el módulo de Terraform que vas a probar Crea la estructura del proyecto. Terratest espera que los tests vivan en un directorio aparte (por convención `test/`) que apunta al código de Terraform: ```bash mkdir -p ~/terratest-demo/{module,test} cd ~/terratest-demo ``` Crea el módulo: ```bash nano module/main.tf ``` ```hcl terraform { required_version = ">= 1.5" required_providers { local = { source = "hashicorp/local" version = "~> 2.5" } } } variable "environment" { type = string description = "Nombre del entorno" } variable "output_dir" { type = string description = "Directorio donde se escribe el archivo de configuración" } resource "local_file" "app_config" { filename = "${var.output_dir}/app-${var.environment}.conf" content = "environment=${var.environment}\n" file_permission = "0640" } output "config_path" { value = local_file.app_config.filename } output "environment" { value = var.environment } ``` El módulo recibe dos variables, crea un archivo y expone su ruta como salida. Comprueba que la sintaxis es válida antes de escribir el test: ```bash cd module && terraform init -backend=false && terraform validate && cd .. ``` ```output Success! The configuration is valid. ``` ## Paso 4: Inicializar el módulo de Go e instalar Terratest Los tests son código Go normal, así que necesitan su propio `go.mod`. Inicialízalo dentro de `test/`: ```bash cd ~/terratest-demo/test go mod init github.com/your_user/terratest-demo/test ``` Añade Terratest como dependencia. Pide el módulo raíz (`github.com/gruntwork-io/terratest`) y no un subpaquete concreto como `modules/terraform`, porque este último da un error de `ambiguous import`: ```bash go get github.com/gruntwork-io/terratest@latest ``` ```output go: added github.com/gruntwork-io/terratest v1.0.1 ``` `testify`, la biblioteca de aserciones que usarás en el test, se añadirá automáticamente en el siguiente paso con `go mod tidy`. Sustituye `your_user` por tu usuario u organización de GitHub. La ruta del módulo solo sirve para identificarlo; no hace falta que el repositorio exista todavía. ## Paso 5: Escribir el primer test Crea el archivo del test. En Go los tests viven en archivos que terminan en `_test.go` y las funciones empiezan por `Test`: ```bash nano module_test.go ``` ```go package test import ( "os" "path/filepath" "testing" "github.com/gruntwork-io/terratest/modules/random" "github.com/gruntwork-io/terratest/modules/terraform" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestAppConfigModule(t *testing.T) { t.Parallel() // Nombre único para que varios tests en paralelo no choquen. environment := "test-" + random.UniqueId() outputDir := t.TempDir() opts := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ TerraformDir: "../module", Vars: map[string]interface{}{ "environment": environment, "output_dir": outputDir, }, NoColor: true, }) // Destroy se ejecuta al final aunque el test falle. defer terraform.Destroy(t, opts) terraform.InitAndApply(t, opts) // Validar las salidas del módulo. configPath := terraform.Output(t, opts, "config_path") assert.Equal(t, filepath.Join(outputDir, "app-"+environment+".conf"), configPath) assert.Equal(t, environment, terraform.Output(t, opts, "environment")) // Validar el recurso real, no solo el estado de Terraform. content, err := os.ReadFile(configPath) require.NoError(t, err) assert.Equal(t, "environment="+environment+"\n", string(content)) info, err := os.Stat(configPath) require.NoError(t, err) assert.Equal(t, os.FileMode(0o640), info.Mode().Perm()) } ``` Los puntos clave de este patrón son: - `terraform.Options` indica dónde está el código (`TerraformDir`) y qué variables pasarle. - `WithDefaultRetryableErrors` reintenta errores transitorios conocidos, como fallos al descargar proveedores. - `defer terraform.Destroy` garantiza la limpieza. Sin él, un test fallido deja recursos creados (y, en un cloud real, facturando). - El test comprueba el recurso real (el archivo en disco), no solo lo que Terraform cree que ha creado. Descarga las dependencias que usa el test (incluida `testify`) y ordena `go.mod`: ```bash go mod tidy ``` ## Paso 6: Ejecutar el test Ejecuta el test con salida detallada. Sube el tiempo máximo, porque el valor por defecto de Go (10 minutos) se queda corto en cuanto el módulo crea infraestructura real: ```bash go test -v -timeout 30m ./... ``` Terratest muestra cada comando de Terraform que ejecuta. El final de la salida debería ser parecido a este: ```output TestAppConfigModule 2026-09-25T10:12:41Z logger.go:67: Destroy complete! Resources: 1 destroyed. --- PASS: TestAppConfigModule (4.87s) PASS ok github.com/your_user/terratest-demo/test 4.901s ``` Para comprobar que el test detecta errores de verdad, cambia temporalmente `file_permission` a `"0644"` en `module/main.tf` y vuelve a ejecutarlo: ```output Error: Not equal: expected: 0x1a0 actual : 0x1a4 --- FAIL: TestAppConfigModule (4.62s) ``` Deja de nuevo el valor en `"0640"` antes de continuar. Fíjate en que, aunque el test falla, la salida también muestra `Destroy complete!`: la limpieza se ha ejecutado igualmente. > Tip: Para ejecutar un único test de un paquete con muchos, usa `go test -v -timeout 30m -run TestAppConfigModule ./...`. ## Paso 7: Probar módulos que crean servicios de red Con infraestructura real no basta con leer salidas: hay que comprobar que el servicio responde. Terratest incluye el paquete `http_helper`, que reintenta una petición hasta que devuelve el código y el cuerpo esperados. Suponiendo un módulo que devuelve la IP pública de un servidor web en la salida `public_ip`, la validación quedaría así dentro del test: ```go import ( "fmt" "time" http_helper "github.com/gruntwork-io/terratest/modules/http-helper" ) // ...después de terraform.InitAndApply(t, opts) publicIP := terraform.Output(t, opts, "public_ip") url := fmt.Sprintf("http://%s", publicIP) // 30 intentos separados 10 segundos: el servidor puede tardar en arrancar. http_helper.HttpGetWithRetry(t, url, nil, 200, "Hello, World!", 30, 10*time.Second) ``` Los reintentos son necesarios porque un `apply` termina cuando el proveedor crea la máquina, no cuando el servicio que corre dentro está listo. Para módulos de este tipo usa siempre un proyecto o cuenta de pruebas separado de producción, y nombres con `random.UniqueId()` para que dos ejecuciones simultáneas no colisionen. ## Paso 8: Ejecutar los tests en GitHub Actions Sube el proyecto a un repositorio de GitHub y crea el workflow: ```bash mkdir -p ~/terratest-demo/.github/workflows nano ~/terratest-demo/.github/workflows/terratest.yml ``` ```yaml name: terratest on: pull_request: push: branches: [main] jobs: test: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: test/go.mod cache-dependency-path: test/go.sum - uses: hashicorp/setup-terraform@v3 with: terraform_wrapper: false - name: Run Terratest working-directory: test run: go test -v -timeout 30m ./... ``` `terraform_wrapper: false` es importante: el wrapper que instala `setup-terraform` por defecto añade texto a la salida de los comandos y Terratest no puede leer bien los `terraform output`. Si tus módulos usan un proveedor cloud real, pasa sus credenciales como secretos del repositorio en la sección `env` del paso de test. Tras hacer push, la pestaña **Actions** del repositorio mostrará el job `test` con el mismo `--- PASS` que viste en local. ## Solución de problemas - **`panic: test timed out after 10m0s`**: falta `-timeout 30m` (o un valor mayor) en `go test`. - **El test falla leyendo salidas en CI con caracteres extraños**: activa `terraform_wrapper: false` en `hashicorp/setup-terraform`. - **Quedan recursos creados tras un fallo**: asegúrate de que `defer terraform.Destroy` está justo antes de `InitAndApply`, no después. Si el proceso se mata (por ejemplo, cancelando el job), `defer` no llega a ejecutarse; revisa la cuenta de pruebas periódicamente. - **`Error: Failed to query available provider packages`**: problema de red al descargar proveedores. `WithDefaultRetryableErrors` reintenta estos casos; si persiste, comprueba el acceso a `registry.terraform.io`. ## Conclusión Has creado un módulo de Terraform, un test de Terratest que lo despliega, valida tanto las salidas como el recurso real y lo destruye siempre al terminar, y un pipeline que lo ejecuta en cada pull request. Como siguientes pasos, puedes separar las fases de despliegue y validación con el paquete `test_structure` para acelerar la depuración de tests largos, añadir `terraform validate` y `tflint` como comprobaciones rápidas antes de Terratest, y aplicar el mismo enfoque a manifiestos de Kubernetes con el paquete `k8s` de Terratest.