Testing und Validation
terraform validate, tflint, terratest.
Lernziele
Nach dieser Lektion kennst du die Stufen der Terraform-Qualitätssicherung — von fmt über validate und tflint bis zu Integrationstests mit Terratest. Du baust eine sinnvolle Test-Pyramide für IaC und weißt, welche Tests in CI gehören und welche zu teuer für jeden PR sind. Außerdem verstehst du terraform test (native Test-Framework ab Terraform 1.6).
Das Problem: grüner Plan, rote Produktion
terraform validate sagt: dein HCL ist syntaktisch korrekt. terraform plan sagt: die API akzeptiert die Werte — vermutlich. Aber ob dein Modul wirklich zwei Subnets in unterschiedlichen AZs anlegt, ob der Output die erwartete Form hat und ob ein Refactoring die Schnittstelle nicht gebrochen hat, prüft keiner dieser Schritte automatisch.
Ohne Tests verlässt du dich auf manuelles Review — und menschliche Reviewer übersehen count = 0-Bugs, vertauschte Variablen und fehlende Outputs regelmäßig.
Die Test-Pyramide für Terraform
┌─────────────┐
│ Terratest │ teuer, langsam, echter Cloud-/Cluster-Apply
│ (Integration)│
├─────────────┤
│ terraform │ mittel — Plan gegen Mock oder echten Provider
│ test / plan │
├─────────────┤
│ tflint, │ schnell, jeder Commit
│ checkov │
├─────────────┤
│ fmt, │ trivial, jeder Commit
│ validate │
└─────────────┘
Unten breit, oben schmal — wie bei Application-Tests. Nicht jeder PR braucht einen Terratest-Lauf gegen AWS.
Stufe 1: fmt und validate — Pflicht in jeder Pipeline
terraform fmt -check -recursive # schlägt fehl bei Format-Abweichung
terraform init -backend=false # kein Backend nötig für validate
terraform validate
init -backend=false initialisiert Provider lokal ohne Remote Backend — ideal für schnelle CI-Checks. validate prüft Typen, Referenzen und Provider-Schema — kontaktiert aber keine Cloud.
Stufe 2: Linting und Security — tflint, checkov
tflint findet Provider-spezifische Fehler, die validate übersieht — falsche Instance-Types, veraltete Syntax, ungenutzte Declarations:
# .tflint.hcl
plugin "terraform" { enabled = true }
plugin "aws" { enabled = true }
# CI
tflint --init
tflint --recursive
checkov / tfsec scannen auf Security-Misconfigs: öffentliche S3-Buckets, fehlende Verschlüsselung, überbreite Security Groups. Sie laufen statisch über HCL — kein Apply nötig.
checkov -d . --framework terraform
In CI: Lint und Security bei jedem PR; bei ersten Runs viele Findings — priorisiere und unterdrücke bewusst nur dokumentierte False Positives.
Stufe 3: terraform test — natives Framework
Seit Terraform 1.6 gibt es terraform test mit .tftest.hcl-Dateien:
# tests/vpc.tftest.hcl
run "valid_cidr" {
command = plan
module {
source = "./modules/vpc"
}
variables {
name = "test"
cidr = "10.0.0.0/16"
azs = ["eu-central-1a", "eu-central-1b"]
}
assert {
condition = aws_vpc.main.cidr_block == "10.0.0.0/16"
error_message = "VPC CIDR stimmt nicht"
}
}
terraform test
Tests können plan oder apply ausführen — mit -mock-provider für Offline-Tests. Das senkt die Einstiegshürde gegenüber Terratest erheblich.
Stufe 4: Terratest — Integration in echter Cloud
Terratest (Go-Library) applyed Module in echter AWS/GCP/Azure und assertiert Outputs:
func TestVpcModule(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../modules/vpc",
Vars: map[string]interface{}{
"name": "test",
"cidr": "10.1.0.0/16",
"azs": []string{"eu-central-1a", "eu-central-1b"},
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcId := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcId)
}
Kosten und Laufzeit sind real — deshalb: Terratest in nightly Builds oder vor Releases, nicht in jedem PR. defer terraform.Destroy ist Pflicht, sonst laufen Test-Ressourcen weiter.
Was wann testen — pragmatische Regeln
| Test | Frequenz | Blockiert Merge? |
|---|---|---|
| fmt, validate | Jeder PR | Ja |
| tflint, checkov | Jeder PR | Ja (nach Tuning) |
| terraform test (plan) | Jeder PR | Ja |
| Terratest (apply) | Nightly / Release | Warnung |
Module, die viele Teams konsumieren (Plattform-VPC, EKS-Baseline), rechtfertigen mehr Integrationstests als ein Team-spezifischer App-Stack.
Praxis: validate und test ohne Cloud
mkdir test-lab && cd test-lab
cat > main.tf <<'EOF'
variable "length" {
type = number
default = 2
}
resource "random_pet" "server" {
length = var.length
}
output "name" {
value = random_pet.server.id
}
EOF
cat > tests/pet.tftest.hcl <<'EOF'
run "default_length" {
command = plan
assert {
condition = var.length == 2
error_message = "Default length should be 2"
}
}
EOF
terraform init
terraform validate
terraform test
Typische Stolperfallen
Nur validate, nie plan: validate kennt keine Provider-API-Constraints. instance_type = "asdf" scheitert erst bei plan/apply.
Terratest ohne Destroy: Teuerster Fehler — vergessene Test-Ressourcen in Prod-Accounts. Always defer Destroy.
checkov-Noise ignorieren und alles skippen: #checkov:skip=... überall macht den Scan wertlos. Findings triagieren, echte Fixes priorisieren.
Tests gegen Prod-State: Integrationstests brauchen eigene Accounts/Subscriptions oder -backend=false mit lokalem State — nie Prod anfassen.
terraform test mit apply in CI ohne Timeout: Hängende Applies blockieren Pipelines. Timeouts und -parallelism=1 für Test-Runs setzen.
Interview-Vorbereitung
„Wie testest du Terraform?” — Pyramide erklären: fmt/validate (schnell), lint/security (statisch), terraform test (Plan-Assertions), Terratest (Integration). Frequenz und Kosten nennen.
Follow-ups:
- „Unterschied validate vs. plan?” — validate: lokal, kein API-Call; plan: API-Refresh, echter Diff.
- „Was ist Terratest?” — Go-Framework für Apply/Assert/Destroy gegen echte Cloud.
- „Was bringt terraform test?” — Natives .tftest.hcl, plan oder apply, assert-Blöcke; weniger Boilerplate als Terratest.
- „Wann kein Terratest?” — Wenn Kosten/Latenz zu hoch — dann plan-basierte Tests oder Mock Provider.
Zusammenfassung
Terraform-Qualität baut sich in Stufen auf: fmt und validate in jeder Pipeline, tflint und checkov für Lint und Security, terraform test für Plan-Assertions, Terratest für teure Integration in echter Cloud. Die Test-Pyramide verhindert, dass jeder PR Minuten in AWS wartet — und trotzdem Module mit Regressionen durchrutschen. Nicht testen heißt, Produktion zum Testfeld zu machen.
In der nächsten Lektion erzwingen wir Compliance mit Policy as Code.