Инфраструктура как код - это прекрасно ровно до того момента, пока этот код никто не тестирует. А раньше тестировать было больно: единственным взрослым вариантом был Terratest - библиотека на Go, то есть тебе, питонисту или админу, надо было лезть в чужой язык, поднимать go.mod, писать TestMain и реально создавать ресурсы в облаке, чтобы что-то проверить. Половина команды на этом отваливалась.
В этом уроке разберём то, что radикально изменило картину: нативный фреймворк terraform test. Он встроен в сам Terraform, не требует ни строчки Go, а с приходом mock-провайдеров умеет проверять логику без единого реального ресурса в Yandex Cloud. Плюс пройдёмся по статическому анализу - валидации, линтерам и policy as code, которые ловят проблемы ещё до того, как ты вообще что-то применил.
Откуда это взялось и что под капотом
Нативный фреймворк тестирования стал стабильным в Terraform 1.6 (это был конец 2023-го), а уже в 1.7 завезли главную фишку для нашей темы - mock-провайдеры. К середине 2026-го это давно не эксперимент: на актуальных ветках Terraform (1.15.x) и в OpenTofu (он шагнул дальше и сейчас на 1.12.x, тоже со своим terraform test и моками) это рабочий инструмент по умолчанию. То есть если у тебя свежий бинарник - всё уже внутри, ставить ничего не надо.
Идея простая до неприличия. Ты кладёшь рядом с модулем файлы с расширением .tftest.hcl (или JSON-вариант, но HCL читается человеком, давай по-человечески). Внутри - один или несколько блоков run. Каждый run - это отдельный сценарий: задаём переменные, запускаем plan или apply, а потом проверяем результат блоками assert. Запускаешь всё командой terraform test - она сама находит файлы, прогоняет сценарии и в конце честно убирает за собой созданные ресурсы.
Ключевой момент, который путает новичков: у run есть параметр command. Если command = plan - Terraform только считает план и проверяет ассерты по плану, ничего в облаке не трогая. Если command = apply (это значение по умолчанию) - реально создаёт ресурсы, проверяет, а в конце разрушает. Plan-проверки быстрые и бесплатные, apply-проверки честные, но медленные и стоят денег. Баланс между ними - твоё инженерное решение.

Первый тест: проверяем план без облака
Допустим, у нас есть модуль, который заводит сеть и виртуалку в Yandex Cloud. Простейший тест на уровне плана - убедиться, что имена и атрибуты вычисляются правильно. Положи это в файл network.tftest.hcl рядом с модулем.
Код: Выделить всё
variables {
network_name = "test-net"
zone = "ru-central1-a"
}
run "validate_network_plan" {
command = plan
assert {
condition = yandex_vpc_network.this.name == "test-net"
error_message = "Имя сети должно совпадать с входной переменной"
}
assert {
condition = yandex_vpc_subnet.this.zone == "ru-central1-a"
error_message = "Подсеть создаётся не в той зоне"
}
}
Можно подключать переменные на уровне run, объявлять провайдеры, и даже выстраивать цепочки: один run применяет ресурс, следующий run этим результатом пользуется. Так тестируют сценарии вроде "создали кластер, затем накатили на него настройку".
Mock-провайдеры: тестируем логику за секунды
Теперь главный свежак. Даже plan-проверки требуют, чтобы провайдер сходил в API Yandex Cloud, проверил креды, прочитал data-источники. Это медленно и требует доступа в облако из CI. Mock-провайдеры выбивают эту зависимость напрочь.
Ставишь в run (или в самом файле теста) блок mock_provider - и провайдер больше не ходит в сеть. Вместо реальных значений он отдаёт синтетические данные для computed-атрибутов. А если тебе нужно зафиксировать конкретное значение (например, чтобы id ресурса в тесте был предсказуемым), используешь блоки override_resource, override_data и override_module. Их можно писать прямо в тесте либо вынести в отдельный файл с расширением .tfmock.hcl.
Код: Выделить всё
mock_provider "yandex" {}
run "logic_without_cloud" {
command = plan
override_data {
target = data.yandex_compute_image.ubuntu
values = {
id = "fd8mock0imageid000"
}
}
assert {
condition = yandex_compute_instance.vm.boot_disk[0].initialize_params[0].image_id == "fd8mock0imageid000"
error_message = "VM должна подхватывать image_id из data-источника"
}
}
Terraform test против Terratest: когда что
Раз уж есть нативный инструмент, нужен ли вообще Terratest? Нужен, но реже. Раскладка простая.
- terraform test - бери по умолчанию. Юнит-логика модулей, проверка планов, валидация переменных, быстрые сценарии на моках. Не требует Go, живёт рядом с кодом, читается любым, кто знает HCL.
- Terratest - когда нужна тяжёлая интеграция за пределами Terraform. Например, поднять инфру, потом сходить по HTTP на развёрнутый сервис, проверить ответ, дёрнуть SDK облака, выполнить SSH-команду на ВМ. Всё, что выходит за рамки "проверить значения в плане/состоянии", - это территория Go и Terratest.
Статический анализ: ловим беду до запуска
Тесты тестами, но дешевле всего отлавливать проблемы вообще без выполнения - статикой. Вот твой минимальный джентльменский набор.
- terraform fmt - форматирование. В CI запускай как terraform fmt -check -recursive, чтобы пайплайн краснел на кривых отступах.
- terraform validate - проверка синтаксиса и внутренней согласованности конфигурации без обращения в облако. Дешёвый первый барьер.
- tflint - линтер. Знает про специфику провайдеров через плагины, ругается на устаревшие конструкции, неиспользуемые переменные, подозрительные значения.
- Безопасность: tfsec (давно влился в Trivy, так что чаще запускают именно trivy config) и checkov - сканируют HCL на небезопасные настройки: открытые наружу порты, отсутствие шифрования, публичные бакеты.
- Policy as code: OPA/Conftest (политики на языке Rego) и Sentinel (родной для платных тиров HashiCorp). Это уже про правила твоей организации: "запрещаем ресурсы без тега команды", "только разрешённые типы машин".
Собираем в CI
Порядок шагов в пайплайне выстраивается от дешёвого к дорогому - чтобы быстрые проверки роняли сборку раньше, чем ты потратишь время на тяжёлые.
Код: Выделить всё
# Псевдо-пайплайн, шаги по нарастанию стоимости
terraform fmt -check -recursive # стиль, миллисекунды
terraform init -backend=false # без подключения к state
terraform validate # синтаксис и схема
tflint --recursive # линт
trivy config . # безопасность (бывший tfsec)
checkov -d . # ещё безопасность, другой набор правил
terraform test # нативные тесты, в т.ч. на моках
Типичные грабли
- Думать, что mock_provider создаёт ресурсы. Нет. Мок ничего не создаёт и не валидирует против реального API. Если в модуле опечатка в имени атрибута, которую отловил бы только настоящий провайдер, мок может её пропустить. Моки - про логику, не про правду облака.
- Все тесты делать на apply. command = apply по умолчанию - и люди забывают это переопределить. В итоге простейший юнит-тест поднимает кластер на пять минут. Где можно - ставь command = plan.
- Путать override_* с переменными. override_resource/override_data подменяют значения уже существующих в конфигурации ресурсов и data-источников, а не объявляют новые. Target должен указывать на реальный адрес из модуля.
- Гонять security-сканеры, но игнорить их вывод. checkov и trivy найдут десятки замечаний на свежем проекте. Не надо чинить всё сразу, но надо явно решить, что подавляешь и почему, иначе CI превратится в постоянно красную лампочку, на которую все забили.
- Забыть про init перед validate. terraform validate требует инициализированных модулей и провайдеров. В CI делай init -backend=false, чтобы не дёргать удалённый state ради простой проверки схемы.
Повтори руками, на любом своём маленьком модуле:
- Создай файл basic.tftest.hcl с одним run на command = plan и хотя бы двумя assert по атрибутам твоих ресурсов. Запусти terraform test.
- Добавь второй run с mock_provider "yandex" {} и одним override_data или override_resource с фиксированным id. Убедись, что он отрабатывает без кредов облака.
- Прогони по модулю terraform fmt -check, terraform validate, а потом tflint и checkov. Посмотри, что они скажут, и реши, что из этого реальная проблема.
- Собери из этих команд один shell-скрипт в правильном порядке - получился твой локальный мини-CI.
- Чем по результату отличается run с command = plan от run с command = apply, и когда какой выбирать?
- Что именно делает mock_provider и какой класс ошибок он принципиально НЕ способен поймать?
- Для чего нужны блоки override_resource, override_data и override_module - и чем они отличаются от объявления переменных?
- В каких задачах нативный terraform test проигрывает Terratest и почему?
Нативный terraform test - твой инструмент по умолчанию для проверки модулей: HCL-файлы .tftest.hcl, блоки run и assert, никакого Go. Mock-провайдеры (mock_provider плюс override_*) позволяют проверять логику инфраструктуры как кода за секунды, без реальных ресурсов в Yandex Cloud и без затрат - это и есть главный сдвиг последних версий. Terratest оставь для тяжёлой интеграции вне Terraform. А поверх тестов выстрой статику: fmt и validate как дешёвый фундамент, tflint как линтер, trivy/checkov для безопасности, OPA или Sentinel для policy as code. Выстрой это в CI от дешёвого к дорогому - и твои terraform plan и apply перестанут быть лотереей.