linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Уроки
  • How it works
  • База знаний
  • Шпаргалка
  • Capstone
  • Собеседование
home/terraform/kb/Тестирование/terratest-basics

kb/testing ── Тестирование ── advanced

Terratest: интеграционные тесты Terraform на Go

Terratest, Go-библиотека от Gruntwork. Поднимает Terraform, ходит по AWS-API проверять что ресурсы реально созданы такими, как ожидаем, затем сносит. Тяжелее нативного `.tftest.hcl`, но даёт то, чего тот не даст: проверки облачного состояния, HTTP-запросы к подъятому сервису, table-driven тесты, retry-логика. Тесты на Go в `_test.go` файлах рядом с модулями.

view as markdownaka: terratest, terraform-go-testing, gruntwork-terratest

Когда Terratest

Нативный .tftest.hcl отлично покрывает: «модуль принимает X, в plan видно Y». Не покрывает: «после apply реально создался бакет с именем Y, ARN совпадает с ожиданием, HTTPS endpoint отвечает 200». Это, Terratest.

Это Go-библиотека. Тесты, _test.go файлы рядом с модулем. Запускается через go test. Внутри теста: terraform.Init, terraform.Apply, любые проверки через AWS SDK (или curl, или kubectl, что нужно), defer terraform.Destroy.

Цена: писать Go, держать Go-окружение в CI, тесты идут минутами.

Минимальный тест

Структура:

examples/
└── s3-public-bucket/
    └── main.tf      # пример вызова твоего модуля
modules/
└── s3-bucket/
    └── main.tf
test/
└── s3_bucket_test.go
go.mod
go.sum

test/s3_bucket_test.go:

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 TestS3BucketCreated(t *testing.T) {
    t.Parallel()
    uniqueID := random.UniqueId()
    bucketName := fmt.Sprintf("test-bucket-%s", uniqueID)
    awsRegion := "us-east-1"
    tfOpts := &terraform.Options{
        TerraformDir: "../examples/s3-public-bucket",
        Vars: map[string]interface{}{
            "name":   bucketName,
            "region": awsRegion,
        },
        NoColor: true,
    }
    defer terraform.Destroy(t, tfOpts)
    terraform.InitAndApply(t, tfOpts)
    gotARN := terraform.Output(t, tfOpts, "bucket_arn")
    assert.Contains(t, gotARN, bucketName)
    aws.AssertS3BucketExists(t, awsRegion, bucketName)
}

Что происходит по строкам:

  1. random.UniqueId(), суффикс, чтобы два параллельных теста не дрались за одно глобально-уникальное имя бакета.
  2. defer terraform.Destroy ставится до apply. Если apply упадёт destroy всё равно выполнится.
  3. terraform.InitAndApply, init + apply -auto-approve.
  4. terraform.Output, читает один output модуля.
  5. aws.AssertS3BucketExists, реальный AWS-API вызов через SDK.

Запуск:

bash
cd test
go test -timeout 30m -v

Долгий timeout не зря, поднять+снести инфру это минуты.

Table-driven

Стандартный go-паттерн: один тест прогоняет N сценариев из таблицы:

go
func TestBucketVariations(t *testing.T) {
    t.Parallel()
    tests := []struct {
        name        string
        versioning  bool
        encryption  bool
    }{
        {"plain", false, false},
        {"versioned", true, false},
        {"encrypted", false, true},
        {"all_features", true, true},
    }
    for _, tc := range tests {
        tc := tc  // closure capture
        t.Run(tc.name, func(t *testing.T) {
            t.Parallel()
            tfOpts := &terraform.Options{
                TerraformDir: "../examples/s3-public-bucket",
                Vars: map[string]interface{}{
                    "name":           fmt.Sprintf("test-%s-%s", tc.name, random.UniqueId()),
                    "versioning":     tc.versioning,
                    "encryption":     tc.encryption,
                },
            }
            defer terraform.Destroy(t, tfOpts)
            terraform.InitAndApply(t, tfOpts)
            // assertions per-variation
        })
    }
}

Запуск конкретного варианта: go test -run TestBucketVariations/versioned.

Retry на eventual consistency

AWS-API не всегда мгновенно консистентен. Бакет «создан», но GetBucketAcl отвечает 404 ещё пару секунд. Terratest даёт retry-обёртки:

go
retry.DoWithRetry(t, "Wait for ACL", 10, 5*time.Second, func() (string, error) {
    acl := aws.GetS3BucketACL(t, awsRegion, bucketName)
    if acl == "" {
        return "", fmt.Errorf("ACL not yet readable")
    }
    return acl, nil
})

10 попыток с 5-сек паузой. Без этого первый CI-run на чистом окружении упадёт случайно, flaky test, ад для команды.

Стейджи: чтобы не разворачивать каждый раз

Долгие тесты, это боль. Терратест умеет «зашить» apply, потом N тестов на том же state'е, потом destroy:

go
func TestStaged(t *testing.T) {
    tfOpts := &terraform.Options{ TerraformDir: "../examples/big" }
    defer test_structure.RunTestStage(t, "destroy", func() {
        terraform.Destroy(t, tfOpts)
    })
    test_structure.RunTestStage(t, "deploy", func() {
        terraform.InitAndApply(t, tfOpts)
    })
    test_structure.RunTestStage(t, "validate", func() {
        // bunch of asserts
    })
}

Запуск: SKIP_destroy=true go test, оставит инфру, прогонит deploy+validate. В следующий раз SKIP_deploy=true SKIP_destroy=true go test, только валидируем. Деплой одной командой, развалидация ста, отдельно.

Что есть в terratest кроме aws и terraform

ПакетЗачем
modules/awsS3, EC2, IAM, RDS, EKS, ..., assertions и helpers
modules/k8sСоздать kubeconfig, kubectl apply, wait for pod
modules/http-helperHTTP-вызовы с retry, проверить что endpoint отвечает
modules/dockerdocker run/exec, для тестов с локальным контейнером
modules/shellRunCommand с capture, что не покрыли остальные
modules/randomUniqueId, Random*, стабильные суффиксы для имён
modules/retryDoWithRetry/DoWithRetryE для flaky-ситуаций

Terratest vs tftest

Аспектtftest (.tftest.hcl)Terratest (Go)
Язык тестаHCLGo
ОкружениеНужен только terraformНужен Go + AWS-creds (или LocalStack)
Что проверяетАтрибуты Terraform-stateState + реальные API + HTTP + любые SDK
СкоростьСекундыМинуты
Mock supportВстроен (mock_provider)Нет, реальные API
Кривая обученияHCL знаешь, пишешьGo или с нуля, или вспоминать
Когда использоватьUnit-тесты модуляIntegration на LocalStack/AWS

Реалистично, оба. tftest гоняешь на каждом PR (быстрый), Terratest, раз в день или на merge в main.

Подводные камни

  • Terratest требует крылатой инфры для CI. Go + AWS-creds + достаточно quota. LocalStack помогает, но не покрывает всё, некоторые сервисы AWS Pro-only.

  • Defer destroy не гарантирует destroy. Если процесс убили kill -9 или у тест-runner истёк timeout, defer не выполнится. Чистка ресурсов остаётся на cron-job (например aws-nuke по тэгу).

  • t.Parallel() без unique names = коллизия. Два теста с одинаковыми bucket-name дерутся за глобальное пространство. Всегда random.UniqueId() в каждом имени.

  • Большие тесты, большие AWS-bill'ы. Один CI-runner поднял EKS-кластер, зафейлился до destroy, EKS живёт сутки = $$. Поставь cloud-quota alert и/или используй LocalStack где возможно.

  • Terratest стабилизировался, но не «standard». Конкуренты: kitchen-terraform (Ruby, устаревает), terraform-compliance (BDD, см. отдельную KB), собственные bash-скрипты. У Terratest самая большая community и лучший AWS-coverage, для интеграционных тестов это де-факто выбор.

§ команды

bash
go mod init testing

Инициализировать Go-модуль для тестов. Сделать один раз.

bash
go get github.com/gruntwork-io/terratest/modules/terraform

Подтянуть основной пакет.

bash
go test -timeout 30m -v ./...

Запустить все тесты. Timeout важен: без него умолчание 10 минут.

bash
SKIP_destroy=true go test -run TestSpecific

Не сносить инфру после теста: оставить для debug.

§ см. также

  • tf-test-mocksMock-провайдеры: mock_provider, override_resource, override_dataMock-провайдер заменяет реальный AWS на синтезированные ответы, тест бежит без облака, секунды вместо минут. Объявляется в `*.tftest.hcl` через `mock_provider "aws"`. Точечно подменить отдельный ресурс или data-source, `override_resource` и `override_data`. Без mock'ов любой `command = apply` будет требовать LocalStack.
  • iac-testing-theoryЧто тестировать в Terraform, а что: не надоИнфраструктура, не приложение, тест-пирамиду применять буквально не стоит. Тестируй контракты модулей, бизнес-правила, сложные expressions, рефакторинги без destroy. Не тестируй что provider работает, что AWS-API отвечает 200, и тривиальный `name = var.name`. Цель, ловить регрессии, не доказывать корректность.
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки