Axiom — тестовый фреймворк для Go, которого нам всегда не хватало
Axiom — это недостающий тестовый runtime для Go, который добавляет фикстуры, шаги, хуки, retry, плагины, метаданные и структурированное выполнение поверх стандартного testing, оставаясь полностью совместимым с ним. Минимум магии, максимум инфраструктуры.
В этой статье я хочу рассказать про Axiom — тестовый фреймворк (а точнее, тестовый runtime-движок) для Go. Но прежде чем говорить о решении, важно четко обозначить саму проблему, которую он закрывает.
Go по своей философии — язык минимализма. Он осознанно избегает сложных абстракций, магии, навороченных DSL и бесконечных расширений. Пакет testing — идеальное отражение этой философии: маленький, прямолинейный, прозрачный. Это прекрасно для простых юнит-тестов: никаких фреймворков, никаких «чёрных ящиков», всё понятно и управляемо.
Но у этой простоты есть обратная сторона.
Go из коробки не предоставляет ничего из того, без чего современные интеграционные и E2E тесты быстро начинают захлебываться в сложности:
- нет фикстур и детерминированного жизненного цикла ресурсов
- нет хуков
- нет шагов (steps)
- нет retries
- нет метаданных (tags, severity, labels…)
- нет централизованного skip
- нет плагинов
- нет отчётности
- нет механизма композиции конфигурации
- нет единой точки управления тестовой инфраструктурой
И это не “недостаток” — это осознанный выбор Go. Но последствия этого выбора становятся болезненными, как только тесты выходят за рамки простых unit-case’ов и превращаются в интеграционные, изоляционные или end-to-end сценарии.
В больших проектах тесты постепенно обрастают логированием, проверками, подготовкой данных, сложными зависимостями, проверками окружения, запуском по тегам, отчётностью в Allure, повторными попытками, параллелизацией и внутренними инструментами. И каждое из этих требований приходится реализовывать вручную — снова и снова, от файла к файлу, от сервиса к сервису. Получается огромный бойлерплейт, дублирование решений, рассыпанная по проекту логика и отсутствие каких-либо централизованных практик.
Именно здесь появляется фундаментальная проблема Go-тестирования:
Go остаётся простым, но тестовые сценарии — нет. Отсутствие инфраструктуры приводит к тому, что команды вынуждены изобретать фреймворк внутри каждого проекта.
Где-то это пара функций-хелперов. Где-то мини-DSL. Где-то громоздкая обёртка вокруг t.Run. Где-то кустарные retries, глобальные счётчики статистики и хаотичный набор тегов.
Каждая команда придумывает свой велосипед — несовместимый, хрупкий и полностью завязанный на локальные соглашения.
Именно эту проблему и решает Axiom.
Axiom — это не «красивый синтаксис» и не «фреймворк ради фреймворка». Это попытка создать недостающий слой инфраструктуры для сложных тестов в Go: структурированный, расширяемый, предсказуемый и полностью совместимый с нативным testing.
Он убирает бойлерплейт. Убирает дублирование. Убирает хаос. Убирает необходимость каждый раз «вручную» собирать тестовый фреймворк.
Axiom даёт возможность писать автотесты так, как они должны выглядеть в 2025 году — чисто, структурировано и без бесконечных костылей.
Проблема
Чтобы понять глубину проблемы, достаточно открыть любой интеграционный или E2E тест в крупном Go-проекте. Почти всегда это выглядит примерно так:
Один тест — и в нём:
- ручные теги
- ручной Allure-вызов
- ручной setup
- ручные фикстуры
- ручной teardown
- ручные проверки
- дублирование инфраструктурного кода
- бизнес-логика + инфраструктурная логика вперемешку
И что самое главное — следующий тест выглядит точно так же.
Например, соседний тест:
Что здесь не так?
На первый взгляд — обычные интеграционные тесты. Но если внимательно присмотреться, становится видно:
1. Нет централизованного управления инфраструктурой
В Go-тестах вся инфраструктура живёт прямо внутри тестов. Каждый файл вручную решает, как инициализировать окружение, открыть базу, создать клиентов, загрузить конфиг и подготовить данные. В результате у каждого теста появляется собственный «локальный фреймворк».
Пока тестов мало, это терпимо. Но с ростом проекта инфраструктура расползается в виде копипаста: разные тесты по-разному создают ресурсы и управляют окружением. Это не просто дублирование — это потеря управляемости. Любое изменение требует правки десятков или сотен тестов, потому что единого источника правды не существует.
2. Теги, метаданные, Allure — всё вручную
Метаданные живут прямо в теле тестов: теги, severity, feature и вызовы Allure прописываются вручную в каждом файле. Пока структура отчётности стабильна, это работает. Но любое изменение — переименование feature, новая группировка, другая фильтрация — превращается в массовый рефакторинг.
У проекта нет конфигурационного слоя, который описывает политику метаданных и отчётности. Всё зашито в тестах и не поддаётся централизованному управлению.
3. Никакого retry
Go не предоставляет механизма повторных запусков. Интеграционные тесты флапают, а retries реализуются вручную: через обёртки, счётчики попыток и условные teardown’ы. Каждый пишет это по-своему, без общей политики и без изоляции между попытками. Такие решения быстро становятся хрупкими и непредсказуемыми.
4. Параллелизация размазана
Параллельность управляется вызовом t.Parallel(), разбросанным по тестам. Это не политика выполнения, а копипаст-флаг. Глобально изменить стратегию запуска или временно отключить параллелизм можно только вручную, проходясь по всему проекту.
5. Фикстур нет как концепта
В Go нет идеи фикстуры как ресурса с жизненным циклом. Базы, клиенты и тестовые данные создаются вручную прямо в тестах: где-то есть cleanup, где-то его забыли; где-то ресурсы кешируются, где-то создаются заново. Нет композиции, декларативности и lazy-инициализации. Инфраструктурный код разрастается и начинает доминировать над логикой теста.
6. Инфраструктурная логика смешана с бизнес-логикой
В одном и том же тесте соседствуют инициализация окружения, подключение сервисов, подготовка данных, вызовы бизнес-методов, проверки и teardown. Тест перестаёт быть проверкой поведения и превращается в линейный сценарий из «подключись», «создай», «вызови», «проверь», «почисти». Со временем такие тесты сложно читать и ещё сложнее поддерживать.
7. Дублирование повсюду
Всё, что должно быть инфраструктурой, повторяется в каждом файле: setup, метаданные, интеграция с Allure, проверки, параллелизация. Каждый тест копирует один и тот же каркас. Вместо одного управляемого слоя инфраструктуры проект получает сотни мелких реализаций, разбросанных по дереву тестов.
8. Никакой возможности централизованно управлять тестовым поведением
В традиционном Go-подходе тестовое поведение рассыпано по файлам. Нельзя централизованно задать правила фильтрации, retry, логирования, отчётности или параллелизма. Любое изменение политики превращается в ручной обход проекта. Пока инфраструктура остаётся распределённой по тестам, управляемый и предсказуемый тестовый контур построить невозможно.
Итог
Такой стиль тестирования неизбежно появляется в Go-проектах без фреймворка. Потому что Go даёт минималистичный testing, но не даёт инфраструктуры.
И именно здесь становится очевидно:
Нужен слой, который объединяет инфраструктурный код, структуру тестов, метаданные, фикстуры, параллелизацию, retry и плагины. Слой, который даёт порядок и композицию. Слой, который избавляет от бойлерплейта.
И этот слой — Axiom.
Пример использования Axiom
После просмотра анти-примеров становится ясно: основной объём кода в интеграционных тестах — это вовсе не тест. Это инфраструктура. И именно она должна быть вынесена из тестов полностью.
Тесту остаётся только сценарий.
Runner: единый слой инфраструктуры
Здесь сосредоточено всё, что раньше приходилось вручную повторять от теста к тесту: инициализация окружения, подключение к БД, создание gRPC-клиентов, настройка Allure, подсчёт статистики, политика retries, контроль параллелизации. Тесты больше не знают, как всё это работает — им это и не нужно.
Runner становится точкой сборки тестовой инфраструктуры: единым, предсказуемым и расширяемым слоем, на который опирается весь тестовый набор.
Fixtures: инфраструктура в минимуме кода
Фикстуры в Axiom — это ленивые ресурсы с детерминированным жизненным циклом. Они создаются только при первом обращении, кешируются на время выполнения теста (или retry-попытки) и автоматически очищаются. Это позволяет выразить инфраструктуру в нескольких чётких функциях и полностью исключить её из тестового кода.
Фикстуры позволяют держать инфраструктурный код компактным, декларативным и полностью изолированным от тестов. Тест получает уже готовые ресурсы — в момент, когда они действительно нужны — и никогда не заботится о том, как они создаются, кешируются или очищаются.
Итог: инфраструктура перестаёт быть «мини-фреймворком внутри каждого теста» и превращается в чистый, предсказуемый слой, оформленный в нескольких небольших функциях.
Тест: Invalid Account (Axiom)
То, что раньше занимало половину файла — подключение клиентов, setup окружения, ручные теги, повторяющиеся конструкции Allure, дублирование логики проверок — теперь сводится к последовательности шагов. Инфраструктура живёт в Runner’e, тест концентрируется только на сценарии.
Этот тест — наконец-то тест, а не смесь из окружения, инфраструктуры, логирования и хаотичных вспомогательных вызовов.
Аналогичный тест до Axiom состоял бы из:
- ручного подключения БД и клиентов,
- копипасты Allure и тегов,
- ручного retry (или отсутствия retry вовсе),
- повторяющихся setup/teardown-конструкций,
- дублирования кода создания пользователей и данных,
- беспорядочного хранения промежуточного состояния.
С Axiom всё это исчезает, потому что:
- инфраструктура вынесена в Runner,
- фикстуры дают декларативный доступ к ресурсам,
- шаги автоматически структурируют сценарий,
- контекст даёт типизированное хранилище данных,
- плагины берут на себя отчётность, теги, хуки и статистику.
Тест остаётся минимальным, выразительным и не содержит ничего, что не относится непосредственно к проверяемому поведению.
Duplicate Request — такой же декларативный и короткий
Второй тест повторяет ту же структуру: сценарий описан шагами, инфраструктура скрыта в Runner’e, данные передаются через контекст, фикстуры обеспечивают ресурсы. Тест остаётся фокусированным на поведении, а не на подготовке окружения.
- Логика теста — это три шага: подготовка, действие, проверка.
- Ни одного вспомогательного вызова: ни клиентов, ни конфигов, ни setup, ни retries.
- Axiom гарантирует, что фикстуры создадутся ровно один раз, шаги будут обработаны плагинами, а метаданные попадут в отчёты.
- Тест читается как сценарий, а не как смесь бизнес- и инфраструктурных обязанностей.
Что важно в этих примерах
Тесты наконец-то перестают быть смесью технологий и начинают быть тем, чем должны быть — описанием поведения системы.
Функциональность Axiom
Axiom — это не просто удобная оболочка над testing.T. Это полноценный тестовый runtime, который формирует предсказуемую инфраструктуру вокруг каждого теста: от метаданных до lifecycle-хуков, от фикстур до плагинов.
Ниже — краткий обзор основных механизмов, которые делает доступными Axiom.
1. Meta: единый слой метаданных (tags, labels, stories, features, severity)
Мета-данные сливаются (Runner → Case), а плагины вроде testallure автоматически превращают их в отчёты. Это значит: никакой ручной интеграции с Allure, никаких копипастных allure.Feature(...) — всё декларативно.
2. Fixtures: ленивые ресурсы с автоматическим cleanup
Фикстуры в Axiom — центральный концепт. Они создаются только тогда, когда тест действительно к ним обращается, автоматически кешируются и автоматически же очищаются после выполнения.
Это избавляет тесты от ручного:
- создания коннекшенов,
- закрытия ресурсов,
- хранения зависимостей в переменных,
- передачи окружения по цепочке.
Фикстуры формируют полноценный DI-контейнер для тестов, но лёгкий, прозрачный и полностью Go-образный.
3. Hooks: предсказуемый lifecycle тестов, шагов и всего тестового раннера
Suite-level (глобальные):
Эти хуки выполняются один раз за весь запуск Runner, вне зависимости от количества тестов.
Хук Когда вызывается
BeforeAll перед запуском первого тест-кейса
AfterAll после последнего тест-кейса (через t.Cleanup)
Идеально для:
- запуска docker-контейнеров / embedded-сервисов;
- прогрева кэша, загрузки конфигурации;
- глобальных метрик;
- общего teardown.
Test-level:
Хук Когда вызывается
BeforeTest перед выполнением тестового сценария (каждого кейса)
AfterTest после выполнения теста, даже при panic
Используется для:
- логирования начала/конца теста;
- создания контекста (trace span, request-id);
- pre/post валидаций.
Step-level:
Хук Когда вызывается
BeforeStep перед выполнением шага
AfterStep после шага (включая panic)
Используется для:
- измерения времени шагов;
- детализированного логирования;
- Allure / tracing интеграции;
- валидации инвариантов между шагами.
Пример: как подключить хуки
Что дают хуки в реальных интеграционных тестах
- централизованное логирование (не нужно писать t.Log везде);
- метрики шагов (время, частота фейлов);
- автоматическая трассировка (начать trace-span в BeforeTest, завершить в AfterTest);
- пред-/пост-проверки (валидация окружения, состояния БД);
- интеграция с репортерами (например, Allure-плагин добавляет step attachments через hooks);
- расширение поведения без изменения тестов.
Важно: хуки работают вместе с Wraps
- WrapTestAction
- WrapStepAction
Именно сочетание Hooks + Wraps превращает Axiom в полноценный execution engine, аналогичный middleware в Gin / Fiber или pytest hooks.
4. Retry: детерминированные повторные попытки теста
Go не даёт retry механизма. Axiom — да.
Каждый retry создаёт полностью новый Config, что значит:
- фикстуры переинициализируются,
- контекст чистый,
- состояние шага не переиспользуется,
- нет скрытых побочных эффектов.
Axiom обеспечивает чистые, изолированные попытки — как это должно работать в реальных E2E тестах.
5. Plugins: расширяемость на уровне runtime
Плагин — это простая функция, которая получает *axiom.Config и регистрирует поведение в его Runtime:
Важно: плагин ничего не исполняет сам. Он лишь подписывается на события тестового runtime.
Плагин может регистрировать обработчики в Runtime и тем самым расширять поведение фреймворка:
- оборачивать выполнение тестов (EmitTestWrap)
- оборачивать выполнение шагов (EmitStepWrap)
- потреблять логи (EmitLogSink)
- потреблять артефакты (EmitArtefactSink)
- модифицировать метаданные (cfg.Meta)
- внедрять контекст (cfg.Context)
- менять правила skip / retry
- подключать отчётность (Allure, JUnit, JSON)
- фильтровать тесты (как testtags)
- собирать статистику (как teststats)
- отправлять события в Sentry / Datadog / ClickHouse
- делать любую кросс-срезовую инфраструктурную логику
Плагин не знает, как именно будет выполняться тест. Он лишь регистрирует реакции на события runtime:
- «тест начался»
- «шаг выполняется»
- «появился лог»
- «появился артефакт»
Таким образом:
- тесты остаются чистыми
- Allure, логирование и метрики не протекают в тестовый код
- ядро фреймворка не зависит от конкретных интеграций
- любое поведение можно добавить или убрать одной строкой
6. Context: структурированное хранение данных между шагами
Тесты часто требуют передать данные:
- между шагами,
- из фикстуры в шаг,
- из setup-логики в действие.
Вместо переменных на верхнем уровне теста:
7. Parameters: типизированные вводные данные
Можно передать параметры тесту:
Это идеально для таблиц тестов, датасетов и декларативных сценариев.
8. Parallel: явный, предсказуемый контроль параллельности
Вместо t.Parallel() в случайных местах:
Параллельность перестаёт быть хаотичным флагом в каждом тесте — она становится политикой.
9. Skip: статический и динамический
Axiom не просто пропускает тест — он пропускает всю окружающую инфраструктуру:
- фикстуры,
- хуки,
- шаги,
- плагины.
Это критично для CI (например, если сервис временно отключён или environment degraded).
Итог
Axiom — это не «удобный синтаксис для тестов». Это полноценная архитектура тестового окружения, в которой:
- Runner задаёт стратегию тестирования;
- Case формирует декларативный контракт теста;
- Config — runtime-снимок окружения;
- Fixtures — источник инфраструктуры;
- Hooks и Wraps — механизм расширения;
- Plugins — точка интеграции с внешним миром;
- Context и Params — безопасная передача данных;
- Retry и Parallel — политики исполнения.
И всё это работает поверх нативного testing, не заменяя его, а дополняя.
Плагины: как Axiom становится бесконечно расширяемым
Плагин в Axiom — это всего лишь функция:
Но при этом она может делать практически всё:
- модифицировать метаданные теста,
- менять правила skip и retry,
- навешивать middleware на тест или шаги,
- собирать статистику,
- внедрять контекст,
- подключать внешние системы (Sentry, Datadog, Prometheus…),
- фильтровать тесты по тегам,
- интегрировать отчётность (например, Allure),
- полностью переписывать жизненный цикл теста.
Плагин выполняется до начала теста, получает доступ к runtime-конфигурации (Config) и может свободно её менять.
Это делает Axiom не просто фреймворком, а платформой.
Встроенные плагины: три примера реальных возможностей
Axiom поставляется с несколькими готовыми плагинами, которые демонстрируют возможности системы.
Tags Plugin: фильтрация тестов по тегам (в том числе из ENV)
Плагин testtags позволяет запускать тесты избирательно:
Теперь будут запускаться только тесты с тегом smoke.
Можно определять правила через переменные окружения:
Тест, который «не прошёл фильтр», автоматически получает:
Ни единой проверки в тестах — всё централизовано.
Stats Plugin: сбор статистики выполнения
Плагин teststats измеряет:
- количество попыток,
- длительность,
- финальный статус (passed / failed / flaky / skipped),
- ошибки,
- метаданные.
Минимальный usage:
После прогона:
Плагин использует хуки BeforeTest и AfterTest, чтобы автоматически считать попытки и определять flaky-тесты — ни одного изменения в тестах.
Allure Plugin: полноценная интеграция с Allure без ручного кода
Плагин testallure превращает ваши тесты и шаги в Allure-структуру без единого ручного вызова:
Тестовый шаг:
Автоматически становится:
Метаданные задаются декларативно:
И автоматически конвертируются в:
Тестовый код не содержит ни одного allure.* вызова.
Помимо шагов и метаданных, testallure также обрабатывает артефакты, которые тест или инфраструктурный код может эмитить во время выполнения.
Пример из теста или клиента:
Плагин testallure автоматически:
- определяет тип артефакта (json, text, bytes)
- корректно добавляет его в Allure как attachment
- логирует предупреждение, если добавление не удалось
Артефакты — это инфраструктурные данные: HTTP-запросы и ответы, payload’ы, ошибки, идентификаторы и промежуточные состояния, которые нужны для отладки и анализа, а не для логики теста.
В Axiom тест не знает, куда именно они пойдут: он лишь эмитит артефакт, плагин решает, как его обработать, а Allure — всего лишь один из возможных consumer’ов. Сегодня это Allure, завтра — QASE, testit, S3, ClickHouse, Loki или собственная система отчётности.
Самое важное — тесты вообще не знают, что используется Allure. Ни шаги, ни артефакты, ни метаданные не привязаны к конкретному репортингу.
Вы можете заменить Allure, подключить несколько репортёров одновременно или отключить репортинг полностью — не изменив ни одной строчки тестов.
Это и есть настоящая интеграция через архитектуру, а не через хелперы.
Пример: плагин на 5 строк, который логирует длительность каждого шага
В Axiom написать свой плагин проще, чем в большинстве фреймворков:
Подключение:
И теперь каждый шаг в любом тесте будет автоматически логировать своё время.
И не нужно править тесты.
Пример: плагин, который автоматически добавляет кореляционный ID во все шаги
Пример: плагин, который делает retry только для тестов с тегом "flaky"
Что важно понять про плагины
Плагины в Axiom — это не “дополнение”, а второй уровень архитектуры, который работает поверх Runner’а и позволяет менять поведение тестовой системы так же свободно, как middleware меняют HTTP-сервер. Это отдельный, изолированный слой логики, который не смешивается с тестами и не заставляет вас писать DSL — он просто подключается или отключается одной строкой.
Плагин может вмешиваться практически в любой аспект работы тестового рантайма: модифицировать метаданные, политику skip/retry/parallel, структуру и порядок шагов, менять хук-цепочку, расширять контекст, управлять фикстурами, добавлять или переопределять TestWraps и StepWraps, фактически меняя сам workflow выполнения теста. Это означает, что плагины могут не только дополнять фреймворк, но и переписывать его поведение, создавая поверх Axiom вашу собственную тестовую платформу.
Именно комбинация плагинов позволяет собрать корпоративный стандарт тестирования: сложные отчёты, централизованное логирование, policy-based retry, динамический skip, интеграцию с observability-системами, метрики, аудит — всё оформляется декларативно и без копипаста.
Плагины превращают Axiom из “фреймворка” в полноценный конфигурируемый тестовый движок. Он не диктует правила, а предоставляет механизм, на основе которого вы строите свою экосистему. Встроенные плагины показывают потенциал. Настоящая сила — в ваших собственных.
Axiom ничего не ограничивает. Он даёт инструменты.
Небольшая ремарка про экосистему
На просторах Go-экосистемы уже существуют библиотеки, которые пытаются закрыть проблему отсутствия test runtime. Чаще всего они делают это, плотно встраивая модель исполнения прямо в тестовый API или в систему отчётности.
Такой подход работает, но имеет цену: execution engine, шаги, хуки, метаданные и репортинг сливаются в один слой. Тесты начинают писаться под конкретный инструмент, а не под testing, и со временем оказываются жёстко с ним связаны.
В этом подходе нет ничего «неправильного» — он просто решает другую задачу. Axiom же сознательно остаётся execution engine’ом, который работает поверх стандартного testing, не подменяя его и не навязывая собственный DSL. Репортинг, логирование и аналитика в нём вынесены в плагины и могут подключаться или отключаться независимо.
Это делает Axiom не альтернативой testing, а недостающим слоем между тестами и их исполнением.
Заключение
Главная ценность Axiom — в том, что он возвращает тестам структуру. Он создаёт недостающий слой инфраструктуры, которого так не хватало в экосистеме Go: единый runtime, предсказуемый жизненный цикл, декларативные шаги, фикстуры, хуки, плагины, централизованную конфигурацию.
При этом Axiom не ломает привычный Go-подход. Он не вводит магии, не подменяет testing.T, не использует рефлексию для скрытых трансформаций и не превращает тесты в DSL. Всё остаётся максимально прозрачным и совместимым с нативным инструментарием Go: вы по-прежнему запускаете тесты командой go test, используете стандартные механики и интеграции CI.
Фреймворк позволяет писать большие, сложные, интеграционные тесты так же чисто и аккуратно, как простые юнит-тесты. Без бойлерплейта. Без копипаста. Без бесконечных «мини-фреймворков» в каждом проекте.
Axiom даёт инфраструктуру. Тестам остаётся только логика.
Если вам близка идея структурного, расширяемого тестового рантайма для Go — попробуйте Axiom в своих проектах. Фреймворк только начинает развиваться, и любая обратная связь невероятно ценна.
⭐ Поставьте звезду репозиторию — это лучший способ поддержать проект и показать, что экосистеме Go действительно нужен подобный инструмент: https://github.com/Nikita-Filonov/axiom
Спасибо за внимание — и пусть ваши тесты будут такими же простыми, как и сам Go.