Как писать API-автотесты на Go: Axiom, Resty, Testify, Allure и запуск в CI/CD
Как писать «скучные» API-автотесты на Go: выносим всю инфраструктуру в Axiom, оставляя в тестах только бизнес-сценарии, и запускаем всё в CI/CD с Allure.
В результате у нас получатся «скучные» автотесты. Скучные — потому что чистые, предсказуемые и легко читаемые. В самих тестах не будет логики инициализации клиентов, конфигураций, логирования или интеграций с внешними системами. Всё это мы вынесем за пределы тестовых сценариев и настроим один раз на уровне тестовой платформы. Отдельно мы также настроим запуск тестов в CI/CD с использованием GitHub Actions и генерацией Allure-отчётов.
Ключевым элементом решения будет Axiom — execution engine для Go-тестов, который расширяет стандартный пакет testing, не заменяя его и не вводя DSL. Axiom позволяет описывать фикстуры, управлять жизненным циклом тестов, повторными запусками, параллелизмом, метаданными и плагинами, сохраняя при этом привычную модель go test. Подробнее о том, что такое Axiom и какую задачу он решает, можно прочитать в документации проекта.
В качестве тестового API мы будем использовать сервис https://dummyjson.com — публичный HTTP-API с фиктивными данными, предназначенный для обучения и тестирования. Он предоставляет набор REST-эндпоинтов и поддерживает CRUD-операции над различными сущностями. В рамках статьи мы будем работать с сущностями users и products, взаимодействуя с ними по HTTP-протоколу.
Клиенты
Прежде чем переходить к написанию автотестов, необходимо подготовить базовый слой взаимодействия с API. Речь идёт не о тестах, а о транспортном уровне, который будет отвечать за HTTP-взаимодействие, логирование запросов и ответов, обработку ошибок и интеграцию с тестовой платформой.
Важно сразу отделить этот слой от самих тестов. Тесты не должны знать, каким HTTP-клиентом мы пользуемся, как настраиваются таймауты или где именно происходит логирование. Их задача — работать с бизнес-операциями и проверять результат.
Начнём с базового HTTP-клиента, поверх которого позже будут построены клиенты для конкретных сущностей.
Конфигурация HTTP-клиента
Конфигурация клиента выносится в отдельную структуру и загружается извне. Это позволяет легко переопределять параметры для разных окружений без изменения кода.
Логирование HTTP-запросов
Такой подход позволяет централизованно управлять логированием HTTP-вызовов и при необходимости легко изменить формат, уровень логов или способ их обработки, не затрагивая тесты.
HTTP-хуки и интеграция с тестовой платформой
Базовый HTTP-клиент сам по себе решает только транспортную задачу. Однако для автотестов этого недостаточно. Нам важно понимать, что именно было отправлено, что вернул сервер, и иметь эту информацию не в логах, а в структурированном виде — доступном для отчётов и отладки.
Resty предоставляет хуки на различных этапах выполнения запроса, и мы используем их как точки интеграции с execution engine. Через эти хуки HTTP-взаимодействие становится частью тестового сценария, а не просто побочным эффектом.
Этот хук срабатывает только в случае ошибки. Он оформляет ошибку как отдельный шаг теста, сохраняет заголовки и тело запроса в виде artefacts и добавляет структурированный лог. Благодаря этому информация об ошибке автоматически попадает в Allure-отчёт и остаётся связанной с конкретным тестом и шагом.
Этот хук делает каждый HTTP-запрос явным шагом теста. В отчётах становится видно, какой запрос был отправлен, с какими заголовками и телом, даже если сам тест не содержит ни одной строки, связанной с HTTP.
После получения ответа мы сохраняем статус и тело ответа как артефакты. Эти данные автоматически прикрепляются к отчёту и не требуют ручного логирования или отладочного кода в тестах.
Таким образом, весь HTTP-трафик становится частью execution-модели теста. Тесты при этом остаются лаконичными и не знают ничего о логировании, артефактах или формате отчётов. Эта логика полностью сосредоточена в одном месте — в HTTP-клиенте и его хуках.
Базовый HTTP-клиент
В качестве HTTP-клиента используется библиотека Resty. Она хорошо подходит для API-тестов, так как предоставляет удобный API, поддержку middleware, хуков и встроенное логирование.
Здесь важно обратить внимание на несколько моментов.
Во-первых, HTTP-клиент не содержит тестовой логики и ассертов. Он работает с context.Context, выполняет HTTP-вызовы и возвращает *resty.Response, не принимая решений о корректности результата. Это позволяет использовать его как в автотестах, так и в других сценариях при необходимости.
При этом в клиент передаётся axiom.Config, который представляет собой тестовый execution-контекст. Он используется исключительно для интеграции с тестовой инфраструктурой: формирования шагов, логов и артефактов, но не связывает клиент с конкретными тестами или testing.T.
Во-вторых, точки расширения (OnBeforeRequest, OnAfterResponse, OnError) изначально интегрированы с Axiom. Через них HTTP-взаимодействие становится частью execution-модели теста: запросы и ответы логируются как шаги, данные сохраняются в виде artefacts для Allure, а отчёты обогащаются технической информацией без необходимости дублировать эту логику в каждом тестовом сценарии.
Клиенты доменных сущностей
Когда базовый HTTP-клиент готов, следующий шаг — перейти от транспортного уровня к доменной модели API. На этом уровне нас уже не интересуют URL, HTTP-методы и детали сериализации. Мы хотим работать с операциями и сущностями предметной области.
Для этого поверх базового HTTP-клиента создаются клиенты для конкретных сущностей — в нашем случае users и products. Каждый такой клиент инкапсулирует:
- контракт API (эндпоинты),
- модели запросов и ответов,
- базовую обработку ошибок.
Структура моделей и схем полностью повторяет API сервиса https://dummyjson.com. Это публичный сервис с фиктивными данными, который часто используют для обучения и тестирования. Его документацию можно посмотреть непосредственно на сайте, там же описаны все доступные эндпоинты и форматы данных.
Клиент для сущности users
Модели данных
Модели описывают JSON-контракты API и используются как для сериализации запросов, так и для десериализации ответов.
Модели намеренно остаются простыми. В них нет бизнес-логики или валидации — это чистое отражение API-контракта.
Реализация клиента
Клиент для сущности users использует базовый HTTP-клиент и предоставляет два уровня методов:
- низкоуровневые методы, возвращающие *resty.Response,
- высокоуровневые методы, которые возвращают уже готовые структуры.
Низкоуровневые методы полезны в тех случаях, когда тесту важно работать со статусами или заголовками напрямую.
На этом уровне происходит минимальная обработка ошибок и десериализация ответа. Все проверки бизнес-логики остаются в тестах.
Клиент для сущности products
Клиент для products полностью повторяет архитектуру клиента users. Это сделано намеренно — единый шаблон упрощает поддержку и масштабирование тестов.
Модели данных
Реализация клиента
Таким образом, на этом этапе у нас сформирован чёткий слой доменных клиентов. Тесты будут работать уже с ними, не зная ничего о HTTP, сериализации или логировании. Это позволяет писать тесты, которые выглядят как сценарии работы с системой, а не как набор сетевых вызовов.
Фикстуры
На этом этапе нам необходимо подготовить слой фикстур. Он будет отвечать за инициализацию конфигурации, логгера и API-клиентов, а также за управление их жизненным циклом в рамках тестов.
Важно сразу отметить, что в Go нет встроенного понятия фикстур. Стандартный пакет testing сознательно не предоставляет механизма для декларативного описания зависимостей теста и управления их жизненным циклом. В результате в реальных проектах часто появляются самодельные решения: глобальные переменные, хелперы и DI-контейнеры вроде go.uber.org/dig.
Такие подходы имеют ряд системных проблем. Они создают жёсткие связи между модулями, плохо масштабируются, не предоставляют явного механизма очистки ресурсов и, как правило, приводят к дублированию кода. При этом сами тесты всё равно начинают зависеть от деталей инфраструктуры.
Фикстура конфигурации
Начнём с фикстуры конфигурации. Она отвечает за загрузку параметров приложения и предоставляет их всем остальным фикстурам и тестам.
Фикстура инициализируется только один раз на тест и кэшируется в рамках его выполнения. При необходимости здесь же можно добавить очистку ресурсов или дополнительную логику.
Фикстура логгера
Логгер также инициализируется через фикстуру. Это позволяет централизованно управлять логированием и при необходимости переиспользовать один и тот же логгер во всех клиентах.
Логгер становится общей зависимостью, которую можно использовать как в HTTP-клиентах, так и в других частях тестовой платформы.
Фикстуры клиентов
Теперь, когда конфигурация и логгер готовы, можно объявить фикстуры для клиентов доменных сущностей. Эти фикстуры собирают все зависимости в одном месте и возвращают полностью готовый к использованию клиент.
Аналогичным образом описывается фикстура для клиента products.
В результате фикстуры образуют отдельный, хорошо изолированный слой. Тесты не занимаются инициализацией зависимостей и не знают, как именно создаются клиенты. Они просто запрашивают нужную фикстуру и работают с уже готовыми объектами.
Хуки и подготовка окружения для Allure
Помимо фикстур и плагинов, Axiom позволяет подключать хуки жизненного цикла выполнения тестов. Хуки используются для подготовки окружения, инициализации внешних инструментов и выполнения действий, которые должны происходить до или после запуска тестов.
В нашем случае нам нужно корректно подготовить окружение для генерации Allure-отчётов. Allure использует переменную окружения ALLURE_RESULTS_PATH, чтобы определить, куда сохранять результаты выполнения тестов. В локальном запуске или в CI эта переменная может быть не задана.
Для этого добавим простой хук, который будет выполняться один раз перед запуском всех тестов.
Этот хук подключается на уровне базового раннера и выполняется автоматически. Тесты при этом не знают ничего о переменных окружения и не содержат кода, связанного с Allure. Вся логика подготовки окружения остаётся централизованной и изолированной в одном месте.
Такой подход хорошо масштабируется: при необходимости можно добавить дополнительные хуки для инициализации тестовых данных, настройки окружений или интеграции с другими инструментами, не затрагивая код тестов.
Раннеры
Ключевая идея заключается в том, что вся инфраструктура настраивается один раз на уровне раннера. Тесты при этом остаются максимально простыми и не содержат кода, связанного с логированием, отчётами, фильтрацией или повторными запусками.
Базовый раннер
Начнём с базового раннера, который будет использоваться всеми тестами в проекте. Он описывает общее поведение тестовой платформы и подключает все необходимые фикстуры и плагины.
В этом месте сосредоточена вся инфраструктурная конфигурация проекта. Если потребуется изменить стратегию повторных запусков, отключить параллелизм или добавить новый плагин, это делается здесь и автоматически применяется ко всем тестам.
Доменные раннеры
В реальных проектах часто требуется логически группировать тесты по доменам или подсистемам. Для этого Axiom предоставляет механизм композиции раннеров через Join.
Доменные раннеры расширяют базовый раннер, добавляя собственные метаданные, но при этом полностью наследуют его конфигурацию.
Раннер для users
Раннер для products
Такой подход позволяет выстраивать иерархию раннеров, сохраняя единый стандарт выполнения тестов и при этом добавляя доменную специфику. Тесты автоматически получают нужные фикстуры, метаданные, плагины и политику выполнения, не требуя ручной настройки.
Тесты
На этом этапе вся инфраструктура уже подготовлена: HTTP-клиенты, фикстуры и раннеры настроены и связаны между собой. Теперь можно переходить к написанию самих тестов.
Ключевая идея здесь простая — тесты не должны знать ничего о платформе. Они не инициализируют клиентов, не читают конфигурацию, не настраивают логирование и не взаимодействуют с Allure напрямую. Всё это уже сделано на уровне фикстур и раннеров.
В тестах остаётся только описание сценария и проверки бизнес-поведения API.
Тесты для сущности users
Создание пользователя
Начнём с теста на создание пользователя.
Тест читается как простой сценарий: подготовка данных, выполнение операции и проверка результата. Ни одной строки, связанной с инфраструктурой или HTTP.
Получение списка пользователей
Этот тест проверяет базовую работоспособность эндпоинта и может использоваться как smoke-тест.
Получение пользователя по идентификатору
Негативный сценарий
Негативные кейсы выглядят точно так же — без специальных хаков или отдельной инфраструктуры.
В результате тесты получаются компактными, легко читаемыми и не зависят от деталей реализации тестовой платформы. Вся инфраструктура остаётся за пределами тестовых сценариев, а сами тесты фокусируются исключительно на проверке поведения API.
Тесты для сущности products
Тесты для сущности products полностью повторяют структуру и подход, использованные для users. Это сделано намеренно: единый шаблон тестов позволяет легко добавлять новые домены и поддерживать читаемость тестового набора по мере роста проекта.
Создание продукта
Тест проверяет корректность создания продукта и соответствие возвращаемых данных переданному запросу. Генерация данных вынесена в тест, чтобы каждый запуск был независимым и не зависел от состояния внешнего сервиса.
Получение списка продуктов
Этот тест может использоваться как smoke-проверка доступности и корректной работы API для продуктов.
Получение продукта по идентификатору
Негативный сценарий
Таким образом, тесты для products не отличаются по структуре от тестов для users. Это демонстрирует, что выбранная архитектура хорошо масштабируется и позволяет добавлять новые доменные области без увеличения сложности тестового кода.
Конфигурация
Для вынесения параметров окружения добавим простой файл конфигурации. Он используется фикстурой конфигурации и позволяет изменять настройки HTTP-клиента без правок кода.
В текущем примере конфигурация содержит только параметры HTTP-клиента, но при необходимости сюда легко добавить настройки для разных окружений, таймауты, флаги или любые другие параметры, необходимые тестовой платформе.
Конфигурация загружается один раз на тест через фикстуру и автоматически становится доступной всем клиентам и тестам.
Запуск на CI/CD
Для автоматического запуска автотестов и публикации отчётов используем GitHub Actions. Воркфлоу будет запускаться при каждом push и pull request в ветку main, выполнять тесты и публиковать Allure-отчёт с историей на GitHub Pages.
Перед использованием воркфлоу необходимо убедиться, что GITHUB_TOKEN имеет права на запись в репозиторий. Это требуется для публикации отчёта в ветку gh-pages.
Заключение
В этой статье мы собрали полноценный пример API-автотестов на Go: от HTTP-клиентов и фикстур до тестов и запуска в CI/CD. В результате получилась система, где тесты остаются простыми и читаемыми, а вся инфраструктурная логика вынесена за их пределы.
Ключевую роль в этом подходе играет Axiom. Он добавляет в Go отсутствующий execution layer и позволяет управлять жизненным циклом тестов, фикстурами, ретраями, параллелизмом и отчётами, не ломая стандартный testing и не вводя DSL. Благодаря этому автотесты действительно становятся «скучными» — сфокусированными на бизнес-поведении, а не на технических деталях.
⭐ Если Axiom оказался полезным и подход откликнулся, проект можно поддержать звездой на GitHub. Это напрямую влияет на его развитие и дальнейшее улучшение экосистемы тестирования на Go.
Все ссылки на код, отчеты и запуски тестов в CI/CD можно найти на моем GitHub: