TerraformDevOpsTestsIaC

Tester son infrastructure Terraform avec Terratest

5 septembre 2026 · Sphinx-Digital

L’infrastructure as code sans tests, c’est du code sans tests. Terratest permet d’écrire des tests Go qui créent de vraies ressources cloud, vérifient qu’elles fonctionnent correctement, puis les détruisent.

Pourquoi Terratest plutôt que terraform validate ?

terraform validate vérifie la syntaxe. terraform plan vérifie la logique. Terratest vérifie que l’infrastructure fonctionne : le bucket S3 est accessible, le serveur répond sur le bon port, le VPC a les bonnes routes.

Structure d’un test Terratest

// test/aws_vpc_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"
    "github.com/stretchr/testify/require"
)

func TestVpcModule(t *testing.T) {
    t.Parallel()  // lancer les tests en parallèle

    awsRegion := "eu-west-1"

    terraformOptions := &terraform.Options{
        TerraformDir: "../modules/vpc",
        Vars: map[string]interface{}{
            "vpc_cidr":     "10.0.0.0/16",
            "environment":  "test",
            "region":       awsRegion,
        },
    }

    // Détruire les ressources à la fin du test, même si le test échoue
    defer terraform.Destroy(t, terraformOptions)

    // Créer les ressources
    terraform.InitAndApply(t, terraformOptions)

    // Récupérer les outputs
    vpcId := terraform.Output(t, terraformOptions, "vpc_id")
    privateSubnetIds := terraform.OutputList(t, terraformOptions, "private_subnet_ids")

    // Vérifier que les ressources existent et ont les bonnes propriétés
    vpc := aws.GetVpcById(t, vpcId, awsRegion)
    assert.Equal(t, "10.0.0.0/16", vpc.CidrBlock)

    assert.Equal(t, 3, len(privateSubnetIds), "Devrait y avoir 3 subnets privés (1 par AZ)")

    // Vérifier qu'aucun subnet privé n'est exposé sur Internet
    for _, subnetId := range privateSubnetIds {
        subnet := aws.GetSubnetById(t, subnetId, awsRegion)
        assert.False(t, subnet.MapPublicIPOnLaunch,
            "Les subnets privés ne doivent pas assigner d'IPs publiques")
    }
}

Tester un serveur EC2

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

    terraformOptions := &terraform.Options{
        TerraformDir: "../examples/web-server",
        Vars: map[string]interface{}{
            "instance_type": "t3.micro",
            "environment":   "test",
        },
    }
    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    publicIp := terraform.Output(t, terraformOptions, "public_ip")
    url := fmt.Sprintf("http://%s:8080/health", publicIp)

    // Retry jusqu'à ce que le serveur réponde (max 5 minutes)
    http_helper.HttpGetWithRetry(t, url, nil, 200, "OK", 30, 10*time.Second)
}

Idempotence et drift detection

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

    options := &terraform.Options{TerraformDir: "../modules/database"}
    defer terraform.Destroy(t, options)

    terraform.InitAndApply(t, options)

    // Un deuxième apply ne doit faire aucun changement
    exitCode := terraform.PlanExitCode(t, options)
    assert.Equal(t, 0, exitCode, "Deuxième plan doit retourner 0 (no changes)")
}

Intégration dans GitLab CI

terraform-test:
  image: golang:1.22
  before_script:
    - apt-get install -y unzip
    - wget -O terraform.zip https://releases.hashicorp.com/terraform/1.6.0/terraform_1.6.0_linux_amd64.zip
    - unzip terraform.zip -d /usr/local/bin/
  script:
    - cd test
    - go mod download
    - go test -v -timeout 30m ./...
  variables:
    AWS_ACCESS_KEY_ID: $TEST_AWS_ACCESS_KEY_ID
    AWS_SECRET_ACCESS_KEY: $TEST_AWS_SECRET_ACCESS_KEY
    AWS_DEFAULT_REGION: eu-west-1
  rules:
    - changes:
        - modules/**
        - test/**

Notre formation Terraform couvre les tests d’infrastructure avec Terratest et l’intégration CI/CD.