Infrastructure Testing dengan Terratest: Validasi Infrastruktur Terraform secara Otomatis

Lhuqita Fazry
Docker & DevOps Terraform Terratest Infrastructure as Code CI/CD Testing
Infrastructure Testing dengan Terratest: Validasi Infrastruktur Terraform secara Otomatis

Mengapa Infrastructure Testing Tidak Bisa Diabaikan dalam IaC Workflow

Ketika kita menulis kode Terraform untuk provisioning infrastruktur, kita sering merasa percaya diri bahwa semuanya akan berjalan lancar. Tapi kenyataannya, infrastruktur yang hanya diandalkan pada pengujian manual menyimpan banyak risiko. Satu kesalahan konfigurasi security group bisa membuka celah akses publik. Satu perubahan pada module terraform bisa menghapus resource yang tidak seharusnya. Dan environment drift antara staging dan production bisa terjadi tanpa kita sadari.

Kita sudah terbiasa menulis unit test untuk kode aplikasi. Kita memverifikasi logika bisnis, menguji edge cases, dan memastikan refactoring tidak merusak fungsionalitas yang sudah ada. Tapi pendekatan yang sama jarang diterapkan ke kode infrastruktur. Padahal pola pikirnya tidak jauh berbeda. Unit test untuk aplikasi fokus pada logika internal, sementara integration test untuk infrastruktur memvalidasi bahwa resource cloud benar-benar terprovisi sesuai harapan.

Di sinilah Terratest hadir. Library Go dari Gruntwork ini memungkinkan kita menulis automated test yang menjalankan Terraform, menginspeksi outputnya, dan memverifikasi state infrastruktur. Terratest mengisi celah penting dalam spektrum pengujian IaC. Bukan sekadar syntax validation yang sudah dilakukan oleh terraform validate, tapi validasi runtime yang memastikan infrastruktur yang kita deploy benar-benar bekerja.

Di organisasi yang menerapkan IaC secara mature, setiap perubahan kode infrastruktur harus melewati automated testing sebelum merger. Tanpa safety net ini, satu pull request yang mengubah security group rule bisa membuka port publik yang tidak seharusnya. Dampaknya bisa berupa downtime berjam-jam atau bahkan insiden keamanan. Terratest memberikan lapisan pertahanan yang sama seperti unit test untuk kode aplikasi.

Memahami Arsitektur Terratest dan Siklus Hidup Test Infrastruktur

Setiap test Terratest mengikuti pola tiga langkah yang konsisten: Apply, Assert, Destroy. Pertama, kita menjalankan terraform init dan terraform apply untuk memprovisi infrastruktur. Kedua, kita membaca nilai output Terraform dan melakukan assertion. Ketiga, kita menghancurkan semua resource dengan terraform destroy. Pola ini memastikan tidak ada resource yang tertinggal setelah test selesai.

Infrastructure as Code testing pyramid menunjukkan lapisan pengujian dari static analysis hingga end-to-end test

Gambar: Testing pyramid untuk Infrastructure as Code menunjukkan hierarki pengujian dari static analysis (dasar) hingga end-to-end integration test (puncak) — Terratest berada di lapisan integration testing — Sumber: [czelabueno/infrastructure-as-code-testing](https://github.com/czelabueno/infrastructure-as-code-testing)

Go memiliki mekanisme defer yang sangat cocok untuk pattern cleanup ini. Kita mendaftarkan terraform.Destroy di awal fungsi test, lalu menjalankan terraform.InitAndApply. Ketika test selesai atau panic sekalipun, Goroutine cleanup tetap berjalan. Ini kunci untuk menjaga environment cloud tetap bersih.

gogo
package test

import (
    "testing"
    "github.com/gruntwork-io/terratest/modules/terraform"
)

func TestInfrastructureCreation(t *testing.T) {
    terraformOptions := &terraform.Options{
        TerraformDir: "../fixtures/simple-infra",
    }

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

Kode di atas adalah skeleton dasar Terratest. Kita mendefinisikan direktori module Terraform melalui TerraformDir, lalu memanggil InitAndApply untuk menjalankan provisioning. Fungsi Destroy dipanggil via defer sehingga cleanup terjadi otomatis setelah assertion selesai atau jika test gagal. Tidak ada resource yang akan tertinggal.

Test ini dijalankan seperti test Go biasa dengan go test. Kita bisa menambahkan flag -v untuk melihat output Terraform secara detail. Jika ada error selama apply atau destroy, Go test runner akan menampilkan kegagalan dengan jelas.

Menulis Test Assertion untuk Validasi Output dan State Terraform

Terraform memungkinkan kita mendefinisikan output yang mengekspos informasi penting seperti ARN resource, DNS endpoint, atau ID security group. Terratest menyediakan terraform.OutputRequired untuk membaca nilai output ini dan membandingkannya dengan ekspektasi.

Kita sering membutuhkan validasi bertingkat: pastikan ALB memiliki DNS name, pastikan S3 bucket memiliki ARN yang valid, pastikan security group membatasi port tertentu. Semua ini bisa kita capai dengan table-driven test di Go.

gogo
func TestInfrastructureOutputs(t *testing.T) {
    terraformOptions := &terraform.Options{
        TerraformDir: "../fixtures/web-infra",
    }

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

    albDns := terraform.OutputRequired(t, terraformOptions, "alb_dns_name")
    bucketArn := terraform.OutputRequired(t, terraformOptions, "s3_bucket_arn")
    sgId := terraform.OutputRequired(t, terraformOptions, "security_group_id")

    if albDns == "" {
        t.Errorf("Expected ALB DNS name to be non-empty, got: %s", albDns)
    }

    t.Logf("ALB DNS: %s", albDns)
    t.Logf("Bucket ARN: %s", bucketArn)
    t.Logf("Security Group ID: %s", sgId)
}

OutputRequired akan memanggil terraform output di belakang layar dan mengembalikan nilai dalam bentuk string. Fungsi ini juga akan memicu error jika output dengan nama tersebut tidak ditemukan. Dengan cara ini kita bisa memverifikasi bahwa setiap komponen infrastruktur benar-benar terbuat dan memiliki atribut yang valid.

Untuk skenario yang lebih kompleks, seperti memvalidasi aturan firewall, kita bisa menggabungkan Terratest dengan AWS SDK untuk memanggil API secara langsung. Misalnya mengambil detail security group dan memeriksa inbound rules yang diizinkan. Kombinasi ini memungkinkan kita memvalidasi tidak hanya bahwa resource terbuat, tapi juga bahwa konfigurasinya benar sesuai standar keamanan perusahaan. Kita bisa memeriksa port, protokol, dan CIDR range yang diizinkan masuk ke dalam environment.

Menguji Respons Infrastruktur terhadap Skenario Kegagalan

Selain validasi standar, kita perlu menguji bagaimana infrastruktur bereaksi terhadap situasi tidak normal. Properti yang paling penting untuk diuji adalah idempotency. Menjalankan terraform apply dua kali berturut-turut seharusnya tidak menghasilkan perubahan apa pun.

Terraform drift detection mendeteksi perubahan infrastruktur yang terjadi di luar Terraform workflow

Gambar: Deteksi drift pada Terraform — infrastruktur yang berubah di luar Terraform workflow akan terdeteksi melalui plan dan dapat diverifikasi dengan Terratest — Sumber: [HashiCorp Blog](https://www.hashicorp.com/blog/terraform-enterprise-adds-projects-drift-detection-and-more)

Terratest menyediakan fungsi terraform.Plan yang bisa kita gunakan untuk mendeteksi drift. Setelah apply pertama, kita bisa memanggil terraform.Plan dan memverifikasi bahwa tidak ada resource yang berubah.

gogo
func TestInfrastructureIdempotency(t *testing.T) {
    terraformOptions := &terraform.Options{
        TerraformDir: "../fixtures/stable-infra",
    }

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

    planOutput := terraform.Plan(t, terraformOptions)
    t.Logf("Plan output:\n%s", planOutput)
}

Jika infrastruktur benar-benar idempoten, output plan akan menunjukkan "No changes. Infrastructure is up-to-date." Jika ada perubahan, itu berarti ada komponen yang tidak stabil atau ada konfigurasi yang terus berubah setiap apply. Kita bisa menambahkan assertion sederhana untuk memeriksa apakah string "No changes" muncul dalam output.

Skenario destructive juga penting untuk diuji. Kita bisa menghapus resource secara manual, misalnya melalui AWS CLI, lalu menjalankan terraform apply lagi untuk memverifikasi bahwa Terraform merekonstruksi resource tersebut tanpa error. Test semacam ini memvalidasi kemampuan recovery infrastruktur secara otomatis.

Perlu diingat bahwa test Terratest memprovisi resource cloud nyata yang akan dikenakan biaya. Kita harus selalu menggunakan tagging yang jelas pada setiap resource dan menerapkan timeout yang ketat. Jika test gagal di tengah jalan, mekanisme defer akan tetap menjalankan destroy, tapi sebaiknya kita juga memiliki scheduled cleanup sebagai lapisan pengaman tambahan.

Mengintegrasikan Terratest ke dalam Pipeline CI/CD

Terratest memberikan nilai maksimal ketika dijalankan otomatis di pipeline CI/CD. Kita bisa memicu test setiap kali ada perubahan pada kode Terraform, memastikan bahwa modifikasi tidak merusak infrastruktur yang sudah ada.

Arsitektur CI/CD pipeline untuk Terraform menggunakan GitHub Actions

Gambar: Arsitektur CI/CD pipeline Terraform dengan GitHub Actions — dari commit code, pull request, plan, review, hingga apply otomatis — Sumber: [Azure-Samples/terraform-github-actions](https://github.com/Azure-Samples/terraform-github-actions)

Untuk menjalankan Terratest di CI, runner membutuhkan akses ke cloud provider. Untuk AWS, kita bisa menggunakan OIDC untuk memberikan credential sementara tanpa menyimpan secret di pipeline. Runner juga perlu memiliki Terraform binary yang terinstal.

GitHub Actions dengan matrix strategy memungkinkan kita menjalankan test untuk beberapa module Terraform secara paralel. Setiap module menggunakan state backend yang terisolasi sehingga tidak saling mengganggu.

yamlyaml
name: Infrastructure Test
on:
  pull_request:
    paths:
      - "terraform/**"

jobs:
  terratest:
    strategy:
      matrix:
        module:
          - networking
          - compute
          - database

    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: "1.22"

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ap-southeast-1

      - name: Terraform Init & Test
        working-directory: test/${{ matrix.module }}
        run: go test -v -timeout 30m

Konfigurasi di atas menjalankan Terratest untuk tiga module sekaligus: networking, compute, dan database. Masing-masing test berjalan di runner terpisah dan memiliki timeout 30 menit untuk mengakomodasi provisioning resource yang lambat.

Kita juga bisa mengoptimalkan kecepatan dengan caching Terraform plugin. Menambahkan langkah caching pada direktori .terraform/ akan mengurangi waktu inisialisasi secara signifikan. Untuk proyek besar, menggunakan flag -target pada resource tertentu juga membantu mempersempit lingkup test.

Struktur direktori test juga mempengaruhi maintainability jangka panjang. Sebaiknya kita pisahkan fixture Terraform untuk setiap modul test agar tidak saling bergantung. Pendekatan ini membuat test lebih mudah dipahami, lebih cepat dijalankan secara paralel, dan lebih mudah didebug ketika ada kegagalan.

Terratest adalah alat yang mengubah cara kita memandang Infrastructure as Code. Bukan lagi sekadar menulis konfigurasi dan berharap semuanya berfungsi. Kita bisa memvalidasi, menguji skenario kegagalan, dan memastikan idempotency secara otomatis. Jika kamu ingin mendalami Infrastructure as Code dan CI/CD pipeline secara lebih sistematis, Rumah Coding memiliki kursus dan bootcamp DevOps yang dirancang untuk membangun kompetensi ini dari dasar hingga production-ready.

Artikel Terkait