TL;DR

  • Terratest prueba infraestructura real via APIs de la nube—no estado de Terraform—detectando bugs que el testing nativo no encuentra
  • El patrón defer terraform.Destroy garantiza limpieza incluso cuando las pruebas fallan, previniendo recursos huérfanos
  • Los test stages te permiten saltar pasos lentos durante desarrollo local (saltar build de AMI, reusar infra desplegada)

Ideal para: Equipos que necesitan validar comportamiento real en la nube, no solo corrección de configuración Omitir si: Solo necesitas validar sintaxis/lógica de Terraform (usa terraform test nativo) Tiempo de lectura: 12 minutos

Aquí hay una verdad dura sobre el testing de infraestructura: el framework de test nativo de Terraform valida estado, no realidad. Si un bug del provider crea un recurso mal configurado pero reporta éxito, tus tests pasan mientras tu infraestructura falla.

Terratest resuelve esto consultando recursos reales en la nube a través de sus APIs nativas. Tu bucket S3 no solo está “creado” en el estado de Terraform—Terratest verifica que realmente existe, tiene el cifrado correcto y sirve contenido correctamente.

Prerrequisitos

Antes de comenzar, asegúrate de tener:

  • Go 1.21+ instalado (go version)
  • Terraform 1.0+ instalado (terraform version)
  • AWS CLI configurado con credenciales (aws sts get-caller-identity)
  • Conocimiento básico de Go (funciones, structs, manejo de errores)
  • Una cuenta AWS dedicada o sandbox para testing

Paso 1: Configuración del Proyecto

Crea una estructura de directorios para tu módulo Terraform y tests:

mkdir -p terraform-s3-module/test
cd terraform-s3-module

Inicializa el módulo Go:

cd test
go mod init github.com/yourorg/terraform-s3-module/test
go get github.com/gruntwork-io/terratest/modules/terraform
go get github.com/gruntwork-io/terratest/modules/aws
go get github.com/stretchr/testify/assert

Tu go.mod debería ahora referenciar Terratest v0.53.0 o posterior.

Paso 2: Crear un Módulo Terraform para Probar

Crea main.tf en el directorio raíz:

variable "bucket_name" {
  description = "Name of the S3 bucket"
  type        = string
}

variable "environment" {
  description = "Environment tag"
  type        = string
  default     = "test"
}

resource "aws_s3_bucket" "this" {
  bucket = var.bucket_name

  tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
  }
}

resource "aws_s3_bucket_versioning" "this" {
  bucket = aws_s3_bucket.this.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
  bucket = aws_s3_bucket.this.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

output "bucket_id" {
  value = aws_s3_bucket.this.id
}

output "bucket_arn" {
  value = aws_s3_bucket.this.arn
}

Paso 3: Escribe Tu Primer Test con Terratest

Crea test/s3_bucket_test.go:

package test

import (
	"fmt"
	"testing"

	"github.com/gruntwork-io/terratest/modules/aws"
	"github.com/gruntwork-io/terratest/modules/random"
	"github.com/gruntwork-io/terratest/modules/terraform"
	"github.com/stretchr/testify/assert"
)

func TestS3BucketCreation(t *testing.T) {
	t.Parallel()

	// Genera nombre único para evitar conflictos
	uniqueID := random.UniqueId()
	bucketName := fmt.Sprintf("terratest-example-%s", uniqueID)
	awsRegion := "us-west-2"

	terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
		TerraformDir: "../",
		Vars: map[string]interface{}{
			"bucket_name": bucketName,
			"environment": "test",
		},
		EnvVars: map[string]string{
			"AWS_DEFAULT_REGION": awsRegion,
		},
	})

	// CRÍTICO: Siempre limpia recursos
	defer terraform.Destroy(t, terraformOptions)

	// Despliega infraestructura
	terraform.InitAndApply(t, terraformOptions)

	// Obtiene outputs
	bucketID := terraform.Output(t, terraformOptions, "bucket_id")

	// Valida infraestructura REAL via AWS API
	aws.AssertS3BucketExists(t, awsRegion, bucketID)

	// Verifica que el versionado esté realmente habilitado (no solo en estado)
	versioning := aws.GetS3BucketVersioning(t, awsRegion, bucketID)
	assert.Equal(t, "Enabled", versioning)

	// Verifica configuración de cifrado
	encryption := aws.GetS3BucketEncryption(t, awsRegion, bucketID)
	assert.Equal(t, "AES256", encryption)
}

Patrones clave en este test:

  1. t.Parallel() — Ejecuta tests concurrentemente para mayor velocidad
  2. random.UniqueId() — Previene colisiones de nombres de recursos
  3. defer terraform.Destroy() — Garantiza limpieza incluso en fallos
  4. aws.AssertS3BucketExists() — Valida recursos AWS reales, no estado

Paso 4: Ejecuta el Test

Ejecuta desde el directorio test:

go test -v -timeout 30m

Salida esperada:

=== RUN   TestS3BucketCreation
=== PAUSE TestS3BucketCreation
=== CONT  TestS3BucketCreation
TestS3BucketCreation 2026-01-11T10:15:30Z command.go:158: Running command terraform with args [init]
TestS3BucketCreation 2026-01-11T10:15:35Z command.go:158: Running command terraform with args [apply -auto-approve]
...
TestS3BucketCreation 2026-01-11T10:16:45Z command.go:158: Running command terraform with args [destroy -auto-approve]
--- PASS: TestS3BucketCreation (95.23s)
PASS

Paso 5: Test Stages para Iteración Más Rápida

Los tests del mundo real son lentos. Construir AMIs, desplegar clusters y ejecutar validaciones puede tomar más de 30 minutos. Los test stages te permiten saltar etapas completadas durante desarrollo:

package test

import (
	"testing"

	"github.com/gruntwork-io/terratest/modules/terraform"
	test_structure "github.com/gruntwork-io/terratest/modules/test-structure"
)

func TestWithStages(t *testing.T) {
	t.Parallel()

	workingDir := test_structure.CopyTerraformFolderToTemp(t, "../", ".")

	// Stage: Deploy
	defer test_structure.RunTestStage(t, "teardown", func() {
		terraformOptions := test_structure.LoadTerraformOptions(t, workingDir)
		terraform.Destroy(t, terraformOptions)
	})

	test_structure.RunTestStage(t, "deploy", func() {
		uniqueID := random.UniqueId()
		terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
			TerraformDir: workingDir,
			Vars: map[string]interface{}{
				"bucket_name": fmt.Sprintf("terratest-%s", uniqueID),
			},
		})
		test_structure.SaveTerraformOptions(t, workingDir, terraformOptions)
		terraform.InitAndApply(t, terraformOptions)
	})

	// Stage: Validate
	test_structure.RunTestStage(t, "validate", func() {
		terraformOptions := test_structure.LoadTerraformOptions(t, workingDir)
		bucketID := terraform.Output(t, terraformOptions, "bucket_id")
		aws.AssertS3BucketExists(t, "us-west-2", bucketID)
	})
}

Salta stages durante desarrollo:

# Salta deploy, solo ejecuta validación (asume que la infra existe)
SKIP_deploy=true go test -v -run TestWithStages

# Salta teardown para mantener recursos para debugging
SKIP_teardown=true go test -v -run TestWithStages

Enfoques Asistidos por IA

En 2026, la IA acelera significativamente el desarrollo con Terratest.

Lo que la IA hace bien:

  • Generar estructura boilerplate de tests a partir de módulos Terraform
  • Escribir aserciones basadas en configuraciones de recursos
  • Sugerir casos límite (¿qué pasa si el nombre del bucket tiene caracteres especiales?)
  • Convertir outputs HCL a mappings de structs en Go

Lo que aún necesita humanos:

  • Decidir qué comportamientos realmente importa probar
  • Entender el radio de impacto si los tests fallan en cuentas de producción
  • Diseñar estrategias de aislamiento de tests
  • Revisar aserciones generadas por IA para falsos positivos

Prompt útil:

Dado este módulo Terraform que crea una instancia RDS:

resource "aws_db_instance" "main" {
  identifier           = var.db_identifier
  engine               = "postgres"
  engine_version       = "15.4"
  instance_class       = var.instance_class
  allocated_storage    = 20
  storage_encrypted    = true
  deletion_protection  = var.enable_deletion_protection
}

Genera un Terratest que:
1. Despliegue la instancia RDS con variables de test
2. Valide que el cifrado esté realmente habilitado via AWS API
3. Verifique que la versión correcta de Postgres esté corriendo
4. Pruebe que la protección contra eliminación esté configurada correctamente
5. Use limpieza apropiada con defer

Incluye manejo de errores y mensajes de aserción significativos.

Framework de Decisión

Cuándo Usar Terratest

Este enfoque funciona mejor cuando:

  • Necesitas validar comportamiento real de recursos en la nube
  • Pruebas interacciones entre múltiples servicios (VPC + EC2 + RDS)
  • Tu equipo tiene experiencia en Go o disposición para aprender
  • Los tests de integración son críticos para tu pipeline de despliegue
  • Estás probando Kubernetes, Docker o Packer junto con Terraform

Considera alternativas cuando:

  • Solo necesitas validar lógica de Terraform (usa terraform test)
  • El feedback rápido es crítico y la curva de aprendizaje de Go es muy pronunciada
  • Tu infraestructura es simple (<5 recursos)
  • Las restricciones de costo limitan el despliegue de recursos efímeros

Terratest vs Terraform Test Nativo

AspectoTerratestTerraform Test
Qué pruebaInfraestructura real via APIsEstado de Terraform
LenguajeGoHCL
Curva de aprendizajeMayorMenor
Detecta bugs de providersNo
Soporte multi-herramientaTerraform, K8s, Docker, PackerSolo Terraform/OpenTofu
VelocidadMás lento (deploys reales)Más rápido (basado en plan)

Mi recomendación: Usa ambos. Tests nativos de Terraform para pruebas unitarias rápidas de lógica de módulos. Terratest para tests de integración que validan infraestructura real antes de despliegues a producción.

Midiendo el Éxito

MétricaLínea BaseObjetivoCómo Medir
Cobertura de tests0%80%+ de módulos críticosContar módulos testeados vs total
Tiempo de ejecuciónN/A< 30 min para suite completaMétricas de pipeline CI
Recursos huérfanosDesconocido0Tags en AWS Cost Explorer
Tasa de tests inestablesAlta< 5%Análisis de fallos CI
Tiempo medio de detecciónDíasHorasTracking de incidentes

Señales de alerta:

  • Tests pasan pero deploys a producción fallan — tests no son suficientemente completos
  • Recursos huérfanos acumulándose — la limpieza no funciona correctamente
  • Tests toman > 1 hora — necesitas paralelizar o usar test stages
  • Alta inestabilidad — falta lógica de retry o problemas de timing

Errores Comunes

1. Falta defer Destroy

// MAL: Si Apply falla, los recursos quedan huérfanos
terraform.InitAndApply(t, options)
terraform.Destroy(t, options) // Nunca se alcanza si Apply falla

// BIEN: La limpieza se ejecuta sin importar el resultado del test
defer terraform.Destroy(t, options)
terraform.InitAndApply(t, options)

2. Colisiones de Nombres de Recursos

// MAL: Conflictuará con otros tests o ejecuciones
bucketName := "my-test-bucket"

// BIEN: Único por ejecución de test
uniqueID := random.UniqueId()
bucketName := fmt.Sprintf("test-%s", uniqueID)

3. Regiones Hardcodeadas

// MAL: Falla si la región por defecto difiere
aws.AssertS3BucketExists(t, "us-east-1", bucketID)

// BIEN: Consistente con el despliegue de Terraform
awsRegion := terraform.Output(t, options, "region")
aws.AssertS3BucketExists(t, awsRegion, bucketID)

4. Timeouts Insuficientes

# MAL: El timeout por defecto de 10m mata tests de larga duración
go test -v

# BIEN: Permite tiempo para infraestructura real
go test -v -timeout 30m

Próximos Pasos

Comienza con un módulo crítico—tu VPC, base de datos principal o cluster de cómputo principal. Escribe un solo test que valide que realmente funciona, no solo que se despliega. Ejecútalo en CI antes de cada merge.

Una vez cómodo, expande para probar interacciones: ¿puede tu aplicación realmente conectarse a esa base de datos? ¿El load balancer rutea el tráfico correctamente?

El objetivo no es 100% de cobertura—es la confianza de que tu infraestructura funciona en la realidad, no solo en la visión de Terraform de la realidad.


Artículos relacionados:

Recursos externos: