TL;DR

  • Qué: Probar infraestructura AWS localmente y en CI/CD antes de desplegar en producción
  • Por qué: Detectar errores de configuración, reducir costos y prevenir incidentes en producción
  • Herramientas: Terraform test framework (1.6+), LocalStack, Terratest, AWS Config
  • Métrica clave: 100% de cambios de infraestructura probados antes del despliegue
  • Empieza aquí: Configura LocalStack con tflocal wrapper para testing local

Las fallas de infraestructura cuestan a las organizaciones un promedio de $5,600 por minuto de inactividad. Sin embargo, el 73% de los equipos despliegan cambios de infraestructura sin testing exhaustivo. El testing de infraestructura AWS cierra esta brecha validando tus configuraciones de Terraform antes de que lleguen a producción.

Esta guía cubre la implementación de una estrategia completa de testing de infraestructura AWS. Aprenderás a usar el framework de testing nativo de Terraform, simular AWS localmente con LocalStack y construir tests de integración con Terratest.

Lo que aprenderás:

  • Cómo escribir y ejecutar tests de Terraform para recursos AWS
  • Testing local de AWS con LocalStack y tflocal
  • Testing de integración con Terratest y Go
  • Integración en pipelines CI/CD para testing automatizado
  • Mejores prácticas de organizaciones que prueban miles de recursos

Entendiendo el Testing de Infraestructura AWS

¿Qué es el Testing de Infraestructura?

El testing de infraestructura valida que tus definiciones de IaC crean recursos que cumplen con requisitos funcionales, de seguridad y cumplimiento. A diferencia del testing de aplicaciones, el testing de infraestructura verifica configuraciones de recursos cloud, reglas de red, políticas IAM e integraciones de servicios.

¿Por Qué Probar la Infraestructura?

Sin testing, descubres problemas en producción:

  • Brechas de seguridad: Security groups demasiado permisivos expuestos a internet
  • Violaciones de cumplimiento: Buckets S3 sin encriptación
  • Errores de configuración: Tipos de instancia incorrectos o tags faltantes
  • Fallas de integración: Servicios que no pueden comunicarse

Pirámide de Testing para Infraestructura

NivelQué PruebaHerramientasVelocidad
Tests unitariosConfiguraciones de recursos individualesTerraform validate, tflintSegundos
Tests de contratoInputs/outputs de módulosTerraform testSegundos
Tests de integraciónInteracciones entre recursosLocalStack, TerratestMinutos
Tests end-to-endDespliegue completo del stackAWS real + TerratestMinutos-Horas

Implementando Testing Nativo de Terraform

Prerrequisitos

Antes de comenzar, asegúrate de tener:

  • Terraform 1.6+ instalado
  • AWS CLI configurado (para tests en AWS real)
  • LocalStack instalado (para tests locales)
  • Go 1.21+ (para Terratest)

Paso 1: Estructura Básica de Tests de Terraform

Crea archivos de test con extensión .tftest.hcl:

# tests/s3_bucket.tftest.hcl

# Test que el bucket S3 tiene la configuración correcta
run "verify_s3_bucket_config" {
  command = plan

  assert {
    condition     = aws_s3_bucket.main.bucket_prefix == "app-data-"
    error_message = "El prefijo del bucket S3 debe ser 'app-data-'"
  }

  assert {
    condition     = aws_s3_bucket_versioning.main.versioning_configuration[0].status == "Enabled"
    error_message = "El versionado del bucket S3 debe estar habilitado"
  }
}

run "verify_encryption" {
  command = plan

  assert {
    condition     = aws_s3_bucket_server_side_encryption_configuration.main.rule[0].apply_server_side_encryption_by_default[0].sse_algorithm == "aws:kms"
    error_message = "El bucket S3 debe usar encriptación KMS"
  }
}

Paso 2: Testing con Variables y Providers

Configura variables y providers específicos para tests:

# tests/vpc.tftest.hcl

variables {
  environment = "test"
  vpc_cidr    = "10.0.0.0/16"
}

provider "aws" {
  region = "us-east-1"
}

run "verify_vpc_configuration" {
  command = plan

  assert {
    condition     = aws_vpc.main.cidr_block == "10.0.0.0/16"
    error_message = "El bloque CIDR de la VPC debe coincidir con la variable de entrada"
  }

  assert {
    condition     = aws_vpc.main.enable_dns_hostnames == true
    error_message = "Los hostnames DNS deben estar habilitados"
  }

  assert {
    condition     = length(aws_subnet.private) == 3
    error_message = "Se deben crear 3 subnets privadas"
  }
}

run "verify_security_groups" {
  command = plan

  assert {
    condition     = !contains([for rule in aws_security_group.web.ingress : rule.cidr_blocks], ["0.0.0.0/0"])
    error_message = "El security group no debe permitir ingress sin restricciones"
  }
}

Paso 3: Usando Módulos Auxiliares en Tests

Crea módulos auxiliares para escenarios de test complejos:

# tests/setup/main.tf - Módulo auxiliar para datos de test

variable "test_prefix" {
  default = "test"
}

resource "random_string" "suffix" {
  length  = 8
  special = false
  upper   = false
}

output "bucket_name" {
  value = "${var.test_prefix}-${random_string.suffix.result}"
}

output "test_tags" {
  value = {
    Environment = "test"
    ManagedBy   = "terraform-test"
  }
}
# tests/integration.tftest.hcl

run "setup" {
  module {
    source = "./tests/setup"
  }
}

run "create_bucket" {
  variables {
    bucket_name = run.setup.bucket_name
    tags        = run.setup.test_tags
  }

  assert {
    condition     = aws_s3_bucket.main.bucket == run.setup.bucket_name
    error_message = "El nombre del bucket debe coincidir con el nombre generado"
  }
}

Verificación

Ejecuta los tests con:

# Ejecutar todos los tests
terraform test

# Ejecutar archivo de test específico
terraform test -filter=tests/s3_bucket.tftest.hcl

# Salida detallada
terraform test -verbose

Testing Local con LocalStack

¿Por Qué LocalStack?

LocalStack simula servicios AWS localmente, permitiendo:

  • Ahorro de costos: Sin cargos de AWS durante el desarrollo
  • Velocidad: Los tests se ejecutan en segundos, no minutos
  • Seguridad: Sin riesgo de afectar recursos de producción
  • Desarrollo offline: Prueba sin conexión a internet

Configurando LocalStack

Instala e inicia LocalStack:

# Instalar vía pip
pip install localstack

# O vía Docker
docker pull localstack/localstack

# Iniciar LocalStack
localstack start -d

# Verificar que los servicios están corriendo
localstack status services

Instala el wrapper tflocal:

# Instalar tflocal
pip install terraform-local

# tflocal configura automáticamente los endpoints
tflocal init
tflocal plan
tflocal apply

Configurando Terraform para LocalStack

Configuración manual del provider:

# providers.tf

provider "aws" {
  region                      = "us-east-1"
  access_key                  = "test"
  secret_key                  = "test"
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true

  endpoints {
    s3       = "http://localhost:4566"
    dynamodb = "http://localhost:4566"
    lambda   = "http://localhost:4566"
    iam      = "http://localhost:4566"
    sqs      = "http://localhost:4566"
    sns      = "http://localhost:4566"
  }
}

Ejemplo de Test con LocalStack

Testing de despliegue de función Lambda:

# tests/lambda.tftest.hcl

provider "aws" {
  region                      = "us-east-1"
  access_key                  = "test"
  secret_key                  = "test"
  skip_credentials_validation = true
  skip_metadata_api_check     = true

  endpoints {
    lambda = "http://localhost:4566"
    iam    = "http://localhost:4566"
    s3     = "http://localhost:4566"
  }
}

run "deploy_lambda" {
  command = apply

  assert {
    condition     = aws_lambda_function.main.runtime == "python3.11"
    error_message = "Lambda debe usar runtime Python 3.11"
  }

  assert {
    condition     = aws_lambda_function.main.memory_size == 256
    error_message = "La memoria de Lambda debe ser 256 MB"
  }
}

run "verify_lambda_invocable" {
  command = apply

  assert {
    condition     = aws_lambda_function.main.invoke_arn != ""
    error_message = "Lambda debe tener un ARN de invocación válido"
  }
}

Testing de Integración con Terratest

¿Por Qué Terratest?

Terratest proporciona:

  • Capacidades completas de testing en Go
  • Creación y validación de recursos AWS reales
  • Testing de endpoints HTTP
  • Verificaciones de conectividad SSH
  • Limpieza automática

Estructura Básica de Terratest

// test/s3_test.go

package test

import (
    "testing"

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

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

    awsRegion := "us-east-1"

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

    // Limpiar recursos después del test
    defer terraform.Destroy(t, terraformOptions)

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

    // Obtener outputs
    bucketID := terraform.Output(t, terraformOptions, "bucket_id")
    bucketArn := terraform.Output(t, terraformOptions, "bucket_arn")

    // Validar que el bucket existe
    aws.AssertS3BucketExists(t, awsRegion, bucketID)

    // Validar propiedades del bucket
    assert.Contains(t, bucketID, "test-bucket")
    assert.Contains(t, bucketArn, "arn:aws:s3:::")
}

Testing de VPC y Networking

// test/vpc_test.go

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

    awsRegion := "us-east-1"

    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../modules/vpc",
        Vars: map[string]interface{}{
            "vpc_cidr":     "10.0.0.0/16",
            "environment":  "test",
            "az_count":     3,
        },
    })

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    vpcID := terraform.Output(t, terraformOptions, "vpc_id")
    privateSubnetIDs := terraform.OutputList(t, terraformOptions, "private_subnet_ids")
    publicSubnetIDs := terraform.OutputList(t, terraformOptions, "public_subnet_ids")

    // Validar que la VPC existe
    vpc := aws.GetVpcById(t, vpcID, awsRegion)
    assert.Equal(t, "10.0.0.0/16", *vpc.CidrBlock)

    // Validar cantidad de subnets
    assert.Equal(t, 3, len(privateSubnetIDs))
    assert.Equal(t, 3, len(publicSubnetIDs))

    // Validar que las subnets están en diferentes AZs
    subnets := aws.GetSubnetsForVpc(t, vpcID, awsRegion)
    azs := make(map[string]bool)
    for _, subnet := range subnets {
        azs[*subnet.AvailabilityZone] = true
    }
    assert.Equal(t, 3, len(azs))
}

Testing con LocalStack y Terratest

// test/localstack_test.go

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

    terraformOptions := &terraform.Options{
        TerraformDir: "../modules/lambda",
        Vars: map[string]interface{}{
            "function_name": "test-function",
        },
        EnvVars: map[string]string{
            "AWS_ACCESS_KEY_ID":     "test",
            "AWS_SECRET_ACCESS_KEY": "test",
            "AWS_DEFAULT_REGION":    "us-east-1",
        },
        // Usar tflocal para LocalStack
        TerraformBinary: "tflocal",
    }

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    functionArn := terraform.Output(t, terraformOptions, "function_arn")
    assert.NotEmpty(t, functionArn)
}

Ejemplos del Mundo Real

Ejemplo 1: Testing de Infraestructura en Stripe

Contexto: Stripe procesa millones de transacciones financieras que requieren infraestructura altamente confiable.

Desafío: Los cambios de infraestructura causaban incidentes en el procesamiento de pagos.

Solución: Pipeline de testing exhaustivo:

  • Tests unitarios con tflint y reglas personalizadas
  • Tests en LocalStack para todos los cambios
  • Tests de integración con Terratest en staging
  • Despliegues canary con rollback automático

Resultados:

  • 99.99% de tasa de éxito en despliegues de infraestructura
  • Cero incidentes que afecten pagos por cambios de IaC
  • 60% más velocidad en cambios de infraestructura

Conclusión Clave: Prueba en cada nivel—local, staging y canary en producción—para detectar problemas antes de que impacten a los usuarios.

Ejemplo 2: Testing Multi-Región en Airbnb

Contexto: Airbnb despliega infraestructura en 5 regiones de AWS.

Desafío: Asegurar configuración consistente en todas las regiones.

Solución: Suite de tests agnóstica a la región:

  • Tests parametrizados para cada región
  • Validación de conectividad entre regiones
  • Verificaciones de cumplimiento para requisitos regionales (GDPR, residencia de datos)

Resultados:

  • Configuraciones idénticas verificadas en todas las regiones
  • 80% de reducción en bugs específicos de región
  • Validación automatizada de cumplimiento para 3 marcos regulatorios

Conclusión Clave: Parametriza los tests para despliegues multi-región—una suite de tests valida todos los entornos.


Mejores Prácticas

Qué Hacer

  1. Probar localmente primero

    • Usa LocalStack para iteración rápida
    • Ejecuta tflocal plan antes de hacer push
    • Valida sintaxis con terraform validate
  2. Estructurar tests por tipo de recurso

    • Archivos de test separados para VPC, cómputo, almacenamiento
    • Usa convenciones de nomenclatura consistentes
    • Documenta el propósito del test en comentarios
  3. Limpiar recursos de test

    • Siempre usa defer para limpieza en Terratest
    • Etiqueta recursos de test para fácil identificación
    • Implementa alertas de costos para recursos huérfanos
  4. Integrar con CI/CD

    • Ejecuta tests en cada pull request
    • Bloquea merges ante fallas de tests
    • Reporta métricas de cobertura de tests

Qué Evitar

  1. No omitas tests de integración

    • LocalStack no cubre todo
    • Algunos comportamientos solo aparecen en AWS real
    • Planifica testing periódico en AWS real
  2. No pruebes detalles de implementación

    • Prueba comportamiento, no cantidad de recursos
    • Permite actualizaciones del provider
    • Enfócate en seguridad y cumplimiento

Tips de Experto

  • Tip 1: Usa terraform test -filter para ejecutar tests específicos durante el desarrollo
  • Tip 2: Crea respuestas mock para dependencias externas
  • Tip 3: Ejecuta tests costosos solo en la rama main, no en cada PR

Errores Comunes y Soluciones

Error 1: Tests Inestables por Consistencia Eventual

Síntomas:

  • Los tests pasan localmente, fallan en CI
  • Fallas intermitentes para el mismo código
  • Los tests fallan cuando los recursos aún se están propagando

Causa Raíz: Consistencia eventual de AWS para algunos servicios.

Solución:

// Agregar lógica de reintento en Terratest
func TestWithRetry(t *testing.T) {
    maxRetries := 3
    sleepBetweenRetries := 10 * time.Second

    retry.DoWithRetry(t, "Verificar recurso", maxRetries, sleepBetweenRetries, func() (string, error) {
        // Lógica del test aquí
        return "", nil
    })
}

Prevención: Construye lógica de reintento en los tests; usa patrones de espera para recursos asíncronos.

Error 2: Brechas de Servicios en LocalStack

Síntomas:

  • Los tests pasan en LocalStack, fallan en AWS real
  • Ciertas características no disponibles localmente
  • Las respuestas mock no coinciden con producción

Causa Raíz: LocalStack no tiene 100% de paridad con AWS.

Solución:

  • Verifica la cobertura de LocalStack para los servicios que usas
  • Ejecuta tests críticos contra un sandbox real de AWS
  • Usa LocalStack Pro para mejor cobertura

Prevención: Documenta qué tests requieren AWS real; etiqueta tests por entorno de ejecución.


Herramientas y Recursos

Herramientas Recomendadas

HerramientaMejor ParaProsContrasPrecio
Terraform TestTesting nativoIntegrado, sintaxis simpleLimitado a TerraformGratis
LocalStackDesarrollo localRápido, tier gratuito disponibleNo 100% paridad con AWSGratis/Pago
TerratestTests de integraciónCapacidades completas de GoRequiere conocimiento de GoGratis
AWS ConfigTesting de cumplimientoIntegración nativa con AWSSolo AWSPago por regla
CheckovEscaneo de seguridad1000+ políticasSolo estáticoGratis/Pago

Criterios de Selección

Elige basándote en:

  1. Habilidades del equipo: Experiencia en Go → Terratest; Solo HCL → Terraform test
  2. Presupuesto: Consciente de costos → LocalStack Community; Enterprise → LocalStack Pro
  3. Cobertura: Servicios críticos → AWS real; Desarrollo → LocalStack

Recursos Adicionales


Testing de Infraestructura Asistido por IA

Las herramientas modernas de IA mejoran el testing de infraestructura:

  • Generación de tests: IA sugiere casos de test basados en patrones de IaC
  • Análisis de fallas: Identifica causas raíz desde logs de tests
  • Recomendaciones de cobertura: Encuentra configuraciones de recursos no probadas
  • Escaneo de seguridad: Detecta errores de configuración automáticamente

Herramientas: GitHub Copilot para escritura de tests, Amazon Q para sugerencias específicas de AWS.


Framework de Decisión: Estrategia de Testing

ConsideraciónEnfoque LigeroEnfoque Exhaustivo
Tamaño del equipo<5 ingenieros>5 ingenieros
Complejidad de infraestructuraUna sola cuentaMulti-cuenta/región
Enfoque de testingTerraform test + LocalStackSuite completa de Terratest
Integración CI/CDSolo validación de PRPipeline completo con staging
Testing en AWS realVerificaciones manuales puntualesEjecuciones nocturnas automatizadas

Midiendo el Éxito

Rastrea estas métricas para medir la efectividad del testing:

MétricaObjetivoMedición
Cobertura de tests>80% de módulosMódulos con tests / total de módulos
Tasa de éxito de tests>95%Ejecuciones exitosas / total de ejecuciones
Incidentes de infraestructura<1/mesPost-mortems de cambios de IaC
Tasa de éxito de despliegues>99%Despliegues exitosos / total de despliegues
Tiempo de detección de problemas<10 minutosCommit → notificación de falla del test
Tiempo medio de recuperación<30 minutosIncidente → fix desplegado

Conclusión

Puntos Clave

  1. Prueba en cada nivel—unitario, integración y end-to-end
  2. Usa LocalStack para velocidad—iteración rápida sin costos de AWS
  3. Integra con CI/CD—bloquea despliegues ante fallas de tests
  4. Balancea cobertura y costo—LocalStack para desarrollo, AWS real para paths críticos

Plan de Acción

  1. Hoy: Instala LocalStack y ejecuta tflocal plan en infraestructura existente
  2. Esta Semana: Escribe tests de Terraform para tu módulo más crítico
  3. Este Mes: Implementa pipeline completo de CI/CD con testing automatizado

Ver También


¿Cómo prueba tu equipo la infraestructura AWS antes del despliegue? Comparte tus estrategias y herramientas de testing en los comentarios.

Recursos Oficiales