Написание интеграционных тестов
dwe test запускает deploy-пайплайн вашего проекта — и любые проверки или команды, которые вы добавите — внутри свежей одноразовой копии проекта. Ничто из происходящего здесь не затрагивает окружение, в котором вы работаете. Это руководство по процессу авторства; пофайловая схема живёт в ../reference/config/tests.md.
Порты изолируются автоматически
Заголовок раздела «Порты изолируются автоматически»dwe test выдаёт каждой изолированной копии свои собственные host-порты, поэтому сценарий работает параллельно с вашим рабочим окружением — и любым другим проектом, занявшим эти порты. Каждый host-порт, объявленный вашими включёнными сервисами под services.<name>.ports, автоматически переназначается на свежевыделенный свободный порт в копии — вы ничего не проводите через vars и не пишете конфигурацию портов в сценарии. И preflight-проверка ports_free, и — для проектов, которые берут свои compose-биндинги из services.<name>.ports (напрямую или через запись exports.env from: services.<name>.ports.<x>) — фактический бинд контейнера читают порт из того же места, поэтому переназначение двигает их вместе.
Шаг сценария, которому нужен переназначенный порт, ссылается на него обычным образом: ${services.<name>.ports.<x>}.
Единственный случай, который это не покрывает, — host-порт, захардкоженный прямо в сыром compose-файле (8080:8080), который ваша dwe-конфигурация сервиса вообще не моделирует — он обходит и переназначение, и preflight ports_free. Либо объявите его под services.<name>.ports, чтобы dwe test мог его увидеть и переназначить, либо проведите подстановку в compose через var и задайте этот var на сценарий через env.vars: { …: auto } (раннер выделит свободный порт и запишет его в vars: копии; шаг тогда читает ${vars.<path>}).
Ваш первый сценарий
Заголовок раздела «Ваш первый сценарий»Создайте workspace/tests/smoke.yml:
description: "Clean deploy comes up healthy"
steps: - name: "app answers" type: builtin cmd: http_check with: url: "http://localhost:${services.app.ports.http}/health" status: 200Запустите:
dwe test run smokeЭто копирует ваш проект в изолированное дерево, генерирует свежий local.yml со свежевыделенными свободными host-портами, выполняет dwe validate, затем реальный dwe deploy run внутри копии, проверяет endpoint и полностью сносит всё после себя. Сценарий вообще без steps: уже полезен как тест — “деплой с этими параметрами проходит успешно”.
Тестирование варианта вашего стека
Заголовок раздела «Тестирование варианта вашего стека»env: описывает, чем окружение этого сценария отличается от значений по умолчанию — переключатели сервисов и переопределения vars:
description: "Deploy with redis disabled — cache falls back to in-memory"
env: services: disable: [redis]
steps: - name: "app still answers without redis" type: builtin cmd: http_check with: url: "http://localhost:${services.app.ports.http}/health" status: 200Каждый файл сценария — это одно изолированное окружение — пишите по одному на каждый значимый вариант (выключенный сервис, переключённый feature-флаг var, другая раскладка портов), а не пытайтесь параметризовать единственный файл.
Проверка не только “порт отвечает”
Заголовок раздела «Проверка не только “порт отвечает”»Шаги используют ровно ту же схему, что и workspace/deploy.yml: type: shell, type: dwe, type: command, type: builtin, с условиями when:. Любой билтин-предикат — file_exists, tcp_reachable, containers_running, env_keys_present, http_check — работает напрямую как тело шага и ведёт себя как проверка pass/fail, а не только внутри check::
steps: - name: "containers are up" type: builtin cmd: containers_running with: { services: [app, db] }
- name: "app answers" type: builtin cmd: http_check with: { url: "http://localhost:${services.app.ports.http}/health", status: 200, contains: "ok" }${...} в with:/cmd: шага резолвится по конфигу копии до выполнения шага, а пути вида file_exists резолвятся относительно корня копии — проверки всегда смотрят на одноразовое окружение, а не на ваше рабочее дерево.
Тестирование проектной команды через type: command
Заголовок раздела «Тестирование проектной команды через type: command»Второй сценарий использования из спецификации — “создать дамп БД, проверить, что файл появился” — это просто шаг type: command, вызывающий обычную пользовательскую команду, за которым следует проверка:
steps: - name: "create dump" type: command cmd: db:dump
- name: "dump file exists" type: builtin cmd: file_exists with: { path: "dumps/db-latest.sql.gz" }Шаги type: command могут вызывать private-команды, так что вы можете держать тест-специфичные команды (например, команду, засеивающую фикстуры, или дампящую в файл с фиксированным именем вместо снабжённого timestamp’ом) вне повседневного листинга dwe commands:
group: testingcommands: - id: db:dump private: true type: shell cmd: "docker compose exec -T db pg_dump -U app app > dumps/db-latest.sql.gz"(hide-команды пайплайны пропускают полностью, поэтому здесь они не работают — используйте private для тест-специфичных команд, которые всё же должны быть запускаемы из сценария.)
Отладка проваленного сценария
Заголовок раздела «Отладка проваленного сценария»Когда сценарий проваливается (сбой деплоя, сбой шага или таймаут), runner пытается собрать отчёт об ошибке в .dwe/tests/reports/<scenario>/ — автоматически, до выполнения teardown, так что он переживает снос окружения. Сбор best-effort: если директорию отчёта создать не удалось, путь остаётся пустым, а отдельные артефакты могут оказаться частичными или пустыми при сбое захвата — но запуск в любом случае сообщает о провале.
dwe test run smoke # проваливается
ls .dwe/tests/reports/smoke/# pipeline.log — лог пайплайна деплоя/шагов сценария# compose-ps.txt — docker compose ps --all внутри копии# container-logs.txt — объединённые логи контейнеров (последние 200 строк каждого)Это тот самый отчёт, который стоит приложить к упавшему CI-запуску или прочитать локально, вообще не трогая Docker. Он перезаписывается на каждом непройденном запуске, поэтому всегда отражает последний сбой.
Когда отчёта недостаточно и нужно осмотреть само живое окружение, перезапустите с --keep:
dwe test run --keep smokeTeardown пропускается (и отчёт об ошибке не собирается, поскольку само окружение сохраняется); dwe test run печатает имя compose-проекта и путь копии, чтобы вы могли зайти внутрь (cd), осмотреть контейнеры через docker compose -p <project> ps или открыть шелл внутри сервиса. Манифест тоже остаётся на диске, поэтому повторный dwe test run smoke откажется стартовать, пока вы не уберёте всё вручную.
Уберите сохранённый запуск — или что угодно, оставшееся после упавшего запуска — командой dwe test clean:
dwe test clean --dry-run # посмотреть, что было бы удалено, ничего не трогаяdwe test clean smoke # снести сохранённое/осиротевшее окружение именно этого сценарияdwe test clean # снести каждое манифестированное окружениеclean манифест-управляемый и никогда не угадывает имя compose-проекта: он сносит только те окружения, для которых есть манифест, пропускает любой сценарий, чей flock в данный момент удерживается живым запуском, и отдельно (report-only) перечисляет Docker compose-проекты, похожие на вывод dwe test, но без манифеста — их он лишь помечает для ручного удаления, не снося автоматически.
Ограничение медленного или зависшего шага
Заголовок раздела «Ограничение медленного или зависшего шага»Любой шаг может иметь собственный timeout: — то же опциональное поле движка, что и у шагов deploy.yml. Полезно, когда шаг сценария — это проверка tcp_reachable/http_check (или команда shell), которая должна быстро провалиться, а не выполняться до истечения общего timeout: сценария:
steps: - name: "app answers quickly" type: builtin cmd: http_check timeout: 5s with: url: "http://localhost:${services.app.ports.http}/health" status: 200Отсутствие поля или timeout: 0 оставляет шаг неограниченным. Таймаут ограничивает только тело шага, которое учитывает отмену контекста Go — шаги-подпроцессы (type: shell/type: dwe) и учитывающие ctx билтины покрыты; шаг, заблокированный на интерактивном вводе, не прерывается принудительно (здесь это неактуально, поскольку запуски сценариев всегда неинтерактивны). Полный контракт см. в Полях шага.
Отлов ошибок до того, как что-либо запущено
Заголовок раздела «Отлов ошибок до того, как что-либо запущено»dwe validate tests статически проверяет каждый файл workspace/tests/*.yml — имя сценария, парсинг timeout:, ссылки env.services, схему шага (включая параметры with: билтина и условия when:) и ссылки type: command — не трогая Docker и не поднимая копию. Это достаточно дёшево, чтобы запускать на каждом push в CI, перед куда более медленным dwe test run:
dwe validate testsОн также выводит риски изоляции compose как предупреждения, так что вы можете исправить их до того, как на них заблокируется dwe test run.
Устранение сбоя изоляции
Заголовок раздела «Устранение сбоя изоляции»dwe test run сканирует compose-файлы копии на конструкции, которые обходят разграничение compose по имени проекта — container_name: и буквальные (нешаблонизированные) host-порты блокирующие; volume’ы и сети с external:/явным name: — только предупреждения. Блокирующая находка проваливает сценарий ещё до начала деплоя (teardown всё равно выполняется), с сообщением, называющим проблемную конструкцию:
blocking compose isolation hazard(s), refusing to run: service db sets container_name: myapp-db — bypasses compose project-name scoping and collides with any other project/run using the same fixed name — pass --skip-isolation-check to downgrade to a warningИсправляйте у источника, когда это возможно:
- Буквальный host-порт (
8080:8080в сыром compose-файле) — перенесите его вservices.<name>.ports, чтобыdwe testмог увидеть его и переназначить автоматически (см. Порты изолируются автоматически выше), либо проведите подстановку в compose через var, заданный черезenv.vars: { …: auto }. container_name:— уберите его; compose уже детерминированно именует контейнеры по имени проекта + сервиса, и именно фиксированныйcontainer_name:вызывает столкновение.
Когда находка ложноположительна, или исправить её прямо сейчас непрактично, понизьте каждую находку до предупреждения и продолжайте:
dwe test run --skip-isolation-check smokeСм. Сканер изоляции compose для полного списка отмечаемых конструкций и градации fail/warn.
Запуск всего набора
Заголовок раздела «Запуск всего набора»dwe test run # каждый сценарий под workspace/tests/*.yml, отсортированныеdwe test run smoke db-dump # только эти два, по имениdwe test list # имена сценариев + описанияdwe test run --timeout 5m # переопределить собственный timeout: каждого сценарияdwe test run --skip-isolation-check smoke # понизить блокирующие находки изоляции до предупрежденийdwe test run --parallel 3 # запускать до 3 сценариев одновременноКоды выхода делают это CI-дружелюбным из коробки: 0 — всё прошло, 1 — хотя бы один сценарий провалился, 2 — сценарий не удалось даже подготовить (плохое имя, захваченный lock, ошибка файла сценария). --output json даёт машиночитаемый отчёт о том же результате.
Запуск сценариев параллельно
Заголовок раздела «Запуск сценариев параллельно»Каждый сценарий уже выполняется полностью изолированно — своя копия, свой compose-проект, свои автоматически перемапленные host-порты — так что ничто не мешает запустить несколько одновременно. --parallel N запускает до N сценариев параллельно:
dwe test run --parallel 3 # до 3 одновременно, по всему наборуdwe test run --parallel 2 smoke redis-off # эти два, бок о бокЭффективный параллелизм — min(N, число сценариев), поэтому --parallel 8 на трёх сценариях запускает три воркера. При более чем одном воркере потоковый вывод каждого сценария заменяется компактным живым видом — одна строка на сценарий со спиннером, грубой фазой и временем, финализирующаяся в ✓ <name> passed либо ✗ <name> failed — step "…":
✓ [12s] smoke passed ⠹ [4s] redis-off deploying… ⠹ [6s] cache-on running steps…running 2/3 scenarios…Полный лог деплоя/пайплайна каждого сценария по-прежнему идёт в собственную копию (.dwe/tests/runs/<scenario>/.dwe/logs/test.log), а отчёт непройденного сценария собирается ровно как при последовательном запуске. Запуски через pipe/CI (без TTY) печатают плоские строки scenario <name>: started / scenario <name>: passed вместо живого блока; --output json не зависит от --parallel.
--parallel 1 (по умолчанию) сохраняет сегодняшнее поведение байт-в-байт: полный потоковый вывод пайплайна, по одному сценарию за раз.
Следите за общими кэшами. Сценарии, которые все долбят один и тот же shared: true кэш пакетов (composer, npm), могут конкурировать при совместном запуске — установки на холодный кэш сериализуются на собственном lock-файле пакетного менеджера. Параллельте сценарии с тёплым или непересекающимся кэшем свободно; тяжёлые установки на холодный кэш держите последовательными и подбирайте N под то, что ваш Docker-демон комфортно тянет одновременно.
Смежные ссылки
Заголовок раздела «Смежные ссылки»../reference/config/tests.md— полная схема сценария, модель изоляции, структура.dwe/tests/, порядок teardown,dwe validate tests, сканер изоляции compose, коды выхода, документированные ограничения.../reference/config/deploy/index.md— полная таблица полей шага, включая общее полеtimeout:.../reference/config/deploy/builtins.md— каждый билтин, доступныйsteps:, включаяhttp_checkи семантику предиката-как-проверки.author-project-commands.md— авторство шаговtype: command, которые может вызывать сценарий, включаяprivate-команды.preflight-checks.md— проверкаports_free, которая обеспечивает предпосылку проведённых через vars портов.