API автотесты на Python с запуском на CI/CD и Allure отчетом
Вступление
В этой статье мы разберём процесс написания API автотестов на Python, используя современные best practices. Кроме того, мы настроим их запуск в CI/CD с помощью GitHub Actions и сформируем Allure-отчёт с историей запусков. Цель статьи — не только показать, как писать качественные API автотесты, но и научить запускать их в CI/CD, получая удобные отчёты о результатах.
Мы будем использовать GitHub Actions, но аналогичная конфигурация возможна и для других CI/CD-систем, таких как GitLab CI, CircleCI или Jenkins — отличаться будет только синтаксис. Итоговый Allure-отчёт опубликуем на GitHub Pages, а также настроим сохранение истории запусков.
Тестируемый API — REST API, предоставленный сервисом FakeBank. Это учебное API, позволяющее работать с фейковыми банковскими операциями. Для тестирования будем отправлять запросы к https://api.sampleapis.com/fakebank/accounts.
Технологии
Вот стек инструментов, которые мы будем использовать:
- Python 3.12 — для написания автотестов
- Pytest — тестовый фреймворк
- HTTPX — для отправки запросов к REST API
- Pydantic — для сериализации, десериализации и валидации данных
- Pydantic Settings — для удобной работы с конфигурацией проекта
- Faker — для генерации случайных данных
- Allure — для генерации детализированного отчёта
- jsonschema — для валидации схемы JSON-ответов
- pytest-xdist — для параллельного запуска тестов
Почему HTTPX, а не Requests?
Библиотека Requests хоть и популярна, но уже давно перестала активно развиваться. У неё до сих пор нет встроенной аннотации типов, хотя эта возможность появилась в Python ещё в версии 3.5 (а на момент написания статьи скоро выйдет уже 3.14). Прошло более 10 лет, но в Requests так и не добавили полноценную поддержку аннотаций типов, что говорит о слабой эволюции библиотеки.
Что даёт HTTPX по сравнению с Requests:
- Встроенные аннотации типов
- Удобный объект Client для повторного использования соединений
- Event hooks (хуки для обработки событий)
- Полноценная поддержка асинхронных запросов
- Поддержка HTTP/2
- Современная и понятная документация
- Множество других полезных возможностей
Всё это делает HTTPX более гибким, производительным и удобным инструментом. Requests, скорее всего, так и останется на уровне синхронных запросов без поддержки современных возможностей.
Если вы не пишете legacy-код и создаёте новый проект API автотестов на Python, то HTTPX — очевидный выбор.
Почему Pydantic?
Тут всё просто: Pydantic — это стандарт де-факто для работы с данными в Python.
Он позволяет:
- Валидировать данные на основе аннотаций типов
- Удобно сериализовать/десериализовать JSON
- Гарантировать строгую типизацию на уровне данных
- Работать с конфигурацией проекта через Pydantic Settings
У Pydantic почти нет достойных альтернатив. Стандартные dataclasses в Python даже близко не дают такой же функциональности, особенно валидации и строгой типизации данных.
Если вам нужны чистые, предсказуемые и надёжные данные — Pydantic обязателен в любом современном проекте.
Почему pytest?
Потому что pytest — это лучший тестовый фреймворк для Python.
- Гибкость: можно писать как простые, так и сложные тесты
- Мощность: плагины, фикстуры, параметризация, маркировки, перезапуски, интеграция с Allure
- Читаемость: тесты остаются чистыми и понятными
- Популярность: pytest — стандарт для тестирования в Python
На фоне этого Behave, Robot Framework выглядят избыточными и усложняющими жизнь инструментами.
Поэтому если вам нужны мощные, поддерживаемые и удобные API/UI-тесты — pytest это лучший выбор.
Модели для описания структур данных
Прежде чем работать с API https://api.sampleapis.com/fakebank/accounts, необходимо определить структуру данных, которые мы будем отправлять и получать.
Для этого используем Pydantic, так как он:
Мы определим:
- CreateOperationSchema – модель для создания новой операции
- UpdateOperationSchema – модель для обновления данных (используется для PATCH запросов)
- OperationSchema – расширенная модель с id, представляющая конечную структуру операции
- OperationsSchema – контейнер для списка операций
- CreateOperationSchema – используется при создании новой операции. Включает поля для суммы списания (debit), суммы зачисления (credit), категории (category), описания (description) и даты (transaction_date).
- UpdateOperationSchema – предназначена для PATCH-запросов, где можно передавать только изменяемые поля. Все параметры здесь опциональны, так как частичное обновление не требует передачи всех данных.
- OperationSchema – расширенная версия CreateOperationSchema, добавляет поле id, которое присваивается сервером. Используется для представления операции в ответах API.
- OperationsSchema – список операций. Наследуется от RootModel, что позволяет работать с массивами объектов в Pydantic.
Почему Pydantic, а не TypedDict или dataclasses?
Pydantic – это лучшее решение для работы с API-данными.
- TypedDict и NamedTuple – подходят больше для аннотаций типов, но не для валидации данных и сериализации.
- dataclasses – могут быть альтернативой, но у них нет встроенной валидации и сериализации JSON. Они работают хорошо в локальных моделях, но для API Pydantic – гораздо удобнее.
Pydantic позволяет автоматически проверять данные, использовать алиасы для полей и работать с JSON без дополнительных преобразований.
Генерация фейковых данных
При тестировании API нам нужно создавать множество случайных данных для разных сценариев. Чтобы автоматизировать этот процесс и избавиться от ручного заполнения, используем библиотеку Faker и реализуем класс Fake. Класс Fake будет предоставлять удобный интерфейс для генерации нужных значений.
Класс Fake инкапсулирует логику библиотеки Faker и предоставляет удобный API для работы. Теперь вместо множества вызовов Faker().some_method() в коде можно просто использовать fake.some_method().
Теперь добавим фейковую генерацию прямо в модели Pydantic, используя параметр default_factory. Это позволит автоматически заполнять поля случайными значениями при создании модели.
Применение default_factory. Каждое поле получает случайное значение при создании экземпляра модели. Пример работы:
operation=CreateOperationSchema()print(operation)
Вывод (данные случайные):
{ "debit": -25.4, "credit": 87.6, "category": "fuel", "description": "Paid for fuel at a gas station.", "transactionDate": "2025-03-30"}
Настройки API автотестов
Реализуем централизованный подход к управлению настройками для API автотестов. Это позволит легко изменять параметры без необходимости редактировать код.
В данном примере нам нужно хранить только URL API и таймаут запросов, но в будущем можно расширять этот механизм.
Для управления настройками будем использовать Pydantic Settings — удобную библиотеку, которая позволяет загружать переменные окружения в виде Pydantic-моделей.
- Класс HTTPClientConfigНаследуется от BaseModel (Pydantic).Описывает базовые настройки HTTP-клиента:url: HttpUrl — базовый адрес API (Pydantic автоматически проверит, что это корректный URL).timeout: float — таймаут запросов.Добавили @property client_url, чтобы возвращать url в строковом формате.
- Класс SettingsНаследуется от BaseSettings (Pydantic Settings).model_config определяет:Где искать переменные (из файла .env).Кодировку файла (utf-8).Поддержку вложенных переменных (env_nested_delimiter='.').fake_bank_http_client: HTTPClientConfig — добавляет вложенные настройки для HTTP-клиента.
- FAKE_BANK_HTTP_CLIENT.URL — адрес API, который будет использовать HTTP-клиент.
- FAKE_BANK_HTTP_CLIENT.TIMEOUT — таймаут запросов (в секундах).
- Благодаря env_nested_delimiter='.', переменные в файле .env автоматически конвертируются в вложенные структуры внутри Settings.
Теперь можно просто инициализировать Settings и использовать его:
Важно! Глобальную переменную settings добавлять не будем — вместо этого будем инициализировать settings на уровне фикстур.
API клиенты
Теперь реализуем API клиент для работы с API https://api.sampleapis.com/fakebank/accounts. Однако перед этим создадим базовый API клиент, который будет использоваться для выполнения стандартных HTTP-запросов. В качестве HTTP-клиента будем использовать httpx.Client.
- BaseClient — класс, который инкапсулирует базовые HTTP-методы (GET, POST, PATCH, DELETE) для взаимодействия с API. Для каждого метода добавлен декоратор allure.step, который позволяет отслеживать шаги выполнения тестов в отчете.
- get_http_client — функция для создания экземпляра httpx.Client с необходимыми настройками (например, URL и таймаут), переданными через конфигурацию.
Важно! Чтобы избежать ошибок и дублирования адресов эндпоинтов в проекте, рекомендуется вынести все URI в отдельный Enum. Это позволит централизованно управлять URL-адресами и избежать опечаток.
- APIRoutes — перечисление всех возможных эндпоинтов, с которыми будет работать приложение. Это позволяет централизовать и стандартизировать использование адресов.
- В реальных проектах вам возможно придется добавлять новые маршруты, и это будет намного удобнее, если они будут прописаны в одном месте.
Теперь напишем API клиент для работы с операциями, используя API https://api.sampleapis.com/fakebank/accounts. Опишем методы, которые необходимо реализовать для работы с операциями в API:
- get_operations_api — получит список операций.
- get_operation_api — получит информацию об операции по operation_id.
- create_operation_api — создаст операцию и вернет её данные.
- update_operation_api — обновит операцию по operation_id и вернет обновленные данные.
- delete_operation_api — удалит операцию по operation_id
- OperationsClient — класс, который наследует от BaseClient и предоставляет методы для работы с операциями (получение списка операций, создание, обновление, удаление операции).
- Каждый метод аннотирован с помощью @allure.step, что позволяет добавлять шаги в отчет Allure. Это помогает отслеживать выполнение шагов и параметров запросов в тестах.
- Для сериализации объектов в JSON используется метод model_dump, который поддерживает алиасы и может исключать поля с None значениями.
- get_operations_client — функция для создания экземпляра OperationsClient. Она принимает настройки и использует их для создания HTTP-клиента с нужными параметрами.
- get_http_client — этот метод создает экземпляр httpx.Client с настройками из конфигурации.
Важно! Обратите внимание, что шаги для Allure были добавлены на двух уровнях: для BaseClient и OperationsClient.
- Шаги для BaseClient: На уровне BaseClient шаги содержат подробную техническую информацию о том, какой HTTP-метод использовался (например, GET, POST, PATCH, DELETE), куда был отправлен запрос, с каким телом и параметрами. Это позволяет нам точно видеть все детали запроса, которые были отправлены на сервер. Например, мы можем узнать:Используемый HTTP-метод.URL, по которому был отправлен запрос.Тело запроса (если оно было).
- Шаги для OperationsClient: На уровне OperationsClient шаги отражают описание выполняемых действий с точки зрения бизнес-логики. Здесь не отображаются технические детали (например, сам HTTP-запрос), а скорее бизнесовые действия, такие как "Получение списка операций", "Создание новой операции" и т.д. Это делает отчет Allureболее понятным и ориентированным на бизнес-логику, не перегружая его техническими деталями.В случае, если нужно получить более детальную информацию (например, какие параметры были переданы в запросе или какие методы использовались), можно раскрыть шаги с детальным описанием, где будут представлены все технические детали.
- Декоратор @allure.step: Важно отметить, что мы используем декоратор @allure.step специально, чтобы в отчет автоматически прикреплялись все параметры, передаваемые в методы и функции. Это позволяет нам в отчете видеть не только, какие шаги были выполнены, но и какие данные были переданы в каждом запросе, обеспечивая полную прозрачность всех действий.
Таким образом, с помощью такого подхода мы достигаем:
- Гибкости в отчете, где можно раскрывать нужные детали on-demand.
- Чистоты и понятности отчета, где бизнес-логику и технические детали можно разделить.
Логирование взаимодействий с API
Для удобного анализа логов при запуске на CI/CD, а также при локальной отладке, добавим логирование HTTP-запросов и ответов. Это позволит:
- Видеть, какие запросы отправляются (метод, URL).
- Получать информацию о статусе ответа и причине, если запрос завершился неудачно.
- Анализировать взаимодействие с API без необходимости включать дебаг-режим или просматривать трассировки сети.
При этом важно избежать избыточного логирования, чтобы не засорять логи ненужной информацией. Поэтому мы ограничимся следующими записями:
- Логирование запроса: указываем HTTP-метод и URL.
- Логирование ответа: указываем статус-код, текстовое описание (reason phrase) и URL.
Некоторые QA Automation также добавляют cURL-команды в логи для воспроизведения запросов, но это может излишне увеличивать объем логов. Вместо этого мы можем прикреплять cURL-команду в Allure-отчет, где она будет более полезной в контексте тестов.
Первым шагом создадим функцию-билдер для конфигурации логгера с пользовательскими настройками.
- get_logger(name: str) -> logging.Logger — создает логгер с кастомными настройками.
- Уровень DEBUG — используется, чтобы видеть все события, включая информационные и отладочные.
- Обработчик StreamHandler() — выводит логи в консоль.
- Формат сообщений включает:Время событияИмя логгераУровень логированияСообщение
- Логгер можно переиспользовать в любом файле, вызывая get_logger("Имя").
Библиотека HTTPX предоставляет механизм event hooks, который позволяет выполнять кастомные действия до отправки запроса и после получения ответа. Мы используем этот механизм для логирования.
- log_request_event_hook(request: Request)Логирует HTTP-метод (GET, POST и т. д.) и URL перед отправкой запроса.
- log_response_event_hook(response: Response)Логирует HTTP-статус-код, причину ответа (reason_phrase) и URL после получения ответа.
- Оба хука подключаются к клиенту HTTPX и автоматически выполняются при каждом запросе.
Теперь добавим хук-функции к HTTP-клиенту, чтобы логи автоматически писались при каждом запросе.
- В BaseClient не изменялось поведение запросов – добавились только event hooks.
- В get_http_client(config: HTTPClientConfig):event_hooks["request"] = [log_request_event_hook] → логируем запрос перед отправкой.event_hooks["response"] = [log_response_event_hook] → логируем ответ после получения.
- Теперь при каждом запросе и ответе автоматически создаются записи в логах.
Фикстуры
Реализуем фикстуры, необходимые для корректной и изолированной работы тестов. Нам понадобятся следующие фикстуры:
- function_operation – создаёт новую операцию для каждого теста, чтобы её можно было удалить, обновить или получить в тесте.
- operations_client – инициализирует и возвращает API-клиент для работы с операциями. Запускается перед каждым тестом.
- settings – инициализирует настройки один раз на всю тестовую сессию.
Почему Pytest плагины, а не conftest.py?
Для объявления и управления фикстурами будем использовать Pytest плагины. Плагины удобнее и гибче, чем объявления фикстур в conftest.py, потому что:
- Нет необходимости заботиться о расположении conftest.py.
- Фикстуры из плагинов доступны глобально во всём проекте, вне зависимости от структуры тестов.
- Использование conftest.py оправдано только для специфических групп тестов. В противном случае файлы conftest.py разрастаются до 1000+ строк, что затрудняет поддержку.
- @pytest.fixture(scope="session") – фиксирует, что настройка создаётся один раз за всю тестовую сессию.
- settings() возвращает объект Settings, который можно переиспользовать в других фикстурах и тестах.
- Использование этой фикстуры позволяет избежать повторной инициализации настроек в каждом тесте.
- operations_client(settings: Settings)Создаёт экземпляр API-клиента для работы с операциями.Использует настройки settings, передавая их в get_operations_client().
- function_operation(operations_client: OperationsClient)Создаёт операцию перед тестом (operations_client.create_operation()).Передаёт её в тест через yield.Удаляет операцию после завершения теста (operations_client.delete_operation_api(operation.id)).
Почему function_operation, а не просто operation?
Рекомендую использовать принцип именования {scope}_{entity}, где:
- function_ – указывает, что операция создаётся на уровне отдельного теста.
- _operation – указывает, что это объект операции.
Если потребуется аналогичная фикстура с уровнем class, её можно назвать class_operation, без необходимости придумывать сложные названия.
Нужно ли удалять тестовые данные?
Удаление созданных данных оправдано, если тестовые данные не нужны в будущем. Однако, если:
- Данные могут быть полезны для ручных проверок.
- Их можно использовать для отладки ошибок.
то удалять их не стоит.
Добавляем файлы с фикстурами в корневой conftest.py, чтобы они подключались автоматически:
Теперь фикстуры settings, operations_client и function_operation будут доступны глобально во всех тестах.
Валидация JSON схемы
Зачем нужна валидация JSON-схемы?
При работе с API важно проверять, соответствует ли возвращаемый JSON-объект заранее определённому контракту. Это позволяет:
- Глобально контролировать структуру данных.
- Быстро выявлять изменения в API, которые могут сломать автотесты.
- Убедиться, что запланированные изменения затрагивают нужные тесты.
- Защититься от случайных изменений, когда разработчик допустил ошибку и API стало возвращать невалидные данные.
Чтобы реализовать такую проверку, будем использовать библиотеку jsonschema, которая позволяет валидировать JSON-объекты согласно заданным схемам.
Реализации функции валидации JSON-схемы
Создадим функцию, которая будет выполнять валидацию JSON-объекта по переданной JSON-схеме.
- Импорт необходимых модулейjsonschema.validate — функция для валидации JSON-объекта.Draft202012Validator.FORMAT_CHECKER — используется для проверки форматов (например, email, дата и т. д.).
- Определение функции validate_json_schemaПринимает JSON-объект (instance) и JSON-схему (schema).Логирует начало процесса валидации.Вызывает validate(), передавая объект и схему.
- Обработка ошибокЕсли объект не соответствует схеме, jsonschema выбросит ValidationError.Если сама схема содержит ошибки, будет вызван SchemaError.
Добавляя такую валидацию в автотесты, мы можем быстро находить проблемы с контрактами API и защищаться от неожиданных изменений. Это помогает поддерживать стабильность тестов и предотвращать регрессии.
Проверки
Зачем нужны проверки?
При тестировании API важно не только вызывать конечные точки, но и проверять, соответствуют ли полученные результаты ожидаемым. Для этого мы реализуем систему проверок.
Прежде чем добавлять проверки для конкретных операций, создадим базовые проверки, которые выполняют низкоуровневые операции, такие как сравнение значений и проверку статус-кода.
Зачем нужны базовые проверки?
- Отображение в Allure-отчётахВсе проверки будут видны как отдельные шаги в отчёте, что упростит анализ тестов.
- Логирование проверокКаждая проверка будет логироваться, что поможет в отладке тестов как локально, так и на CI.
- Снижение бойлерплейт-кодаВместо того чтобы каждый раз писать assert actual == expected, "Ошибка ...", мы будем использовать универсальные функции с уже готовыми сообщениями об ошибках.
Реализация базовых проверок
Создадим модуль содержащий две базовые проверки:
- assert_status_code — проверяет, что статус-код ответа соответствует ожидаемому.
- assert_equal — проверяет, что два значения равны.
- Функция assert_status_codeСравнивает фактический и ожидаемый HTTP-статус ответа.Если статус-коды не совпадают — выдаёт AssertionError с подробным сообщением.
- Функция assert_equalПроверяет, что два значения равны.Использует name для логирования, чтобы было понятно, что именно сравнивается.При несовпадении выдаёт AssertionError с детальным описанием ошибки.
Реализация проверок операций
Теперь реализуем проверки для операций. Они позволят убедиться, что API возвращает корректные данные при создании, обновлении и получении операций.
- assert_create_operation — проверяет, что API вернуло корректные данные после создания или обновления операции.
- assert_operation — полностью проверяет модель операции при её получении.
- Функция assert_create_operationПроверяет корректность данных после создания или обновления операции.Сравнивает debit, credit, category, description и transaction_date.
- Функция assert_operationПроверяет все поля операции, включая её id.Используется при тестировании получения операции.
API тесты
Теперь напишем API автотесты, используя:
- API клиенты для отправки запросов.
- Фикстуры для подготовки тестовых данных.
- Проверки, реализованные ранее.
- Валидацию JSON-схемы, чтобы убедиться, что ответы соответствуют ожидаемой структуре.
Всего у нас будет несколько тестов на базовые CRUD-операции.
- Используем HTTPStatus вместо магических чиселВместо 200, 201, 404 и других кодов используем HTTPStatus.OK, HTTPStatus.CREATED, HTTPStatus.NOT_FOUND. Это делает код читаемым и исключает случайные ошибки.
- Дополнительная проверка после удаленияПосле удаления операции выполняем запрос GET /fakebank/accounts/{id} и проверяем, что сервер вернул 404 Not Found. Это позволяет убедиться, что операция действительно удалена.
- Читаемые заголовки тестов в Allure-отчетеИспользуем @allure.title(), чтобы тесты имели понятные названия в Allure.
- Добавлены pytest маркировки для удобного запуска
Добавим pytest-маркировки в pytest.ini, чтобы избежать предупреждений при запуске.
Благодаря правильно выбранной стратегии написания API автотестов, тесты сфокусированы на проверке бизнес-логики, а не на технических деталях.
Вместо того чтобы загромождать тесты шагами Allure, обработкой данных, валидацией JSON-схем и встроенными проверками, мы выносим эти задачи на другие уровни тестового фреймворка. Такой подход делает тесты:
- Читаемыми – код остается лаконичным и интуитивно понятным.
- Простыми в написании – добавление нового теста требует минимальных усилий.
- Поддерживаемыми – изменения в API или проверках вносятся централизованно, а не в каждом тесте.
В результате тесты сфокусированы исключительно на бизнес-логике, а все вспомогательные процессы (логирование, шаги Allure, взаимодействие с API, выполнение проверок и их сообщения) скрыты на других уровнях фреймворка. Это делает API автотесты эффективными и удобными в работе.
Запуск на CI/CD
Настроим workflow-файл для автоматического запуска API-тестов в GitHub Actions, генерации Allure-отчета с сохранением истории и публикации его на GitHub Pages.
Ссылки на документацию для всех использованных actions можно найти ниже:
Разрешения для Workflow
Если сейчас запустить тесты на GitHub Actions то, будет ошибка, говорящая о том, что у github token из workflow по умолчанию нет прав на записть в репзоиторий
Для исправления этой ошибки необходимо вручную изменить настройки прав workflow:
- Откройте вкладку Settings в репозитории GitHub.
2. Перейдите в раздел Actions → General.
- Прокрутите страницу вниз до блока Workflow permissions.
- Выберите опцию Read and write permissions.
- Нажмите кнопку Save для сохранения изменений.
После выполнения этих шагов можно отправить код с API-тестами в удалённый репозиторий.
Запуск тестов и генерация Allure-отчёта
Если тесты пройдут успешно, Allure-отчёт будет сгенерирован и загружен в ветку gh-pages, после чего автоматически запустится workflow pages build and deployment. Этот процесс публикует Allure-отчёт на GitHub Pages, делая его доступным для просмотра в браузере.
Важно! Перед запуском workflow необходимо убедиться, что в репозитории существует ветка gh-pages. Если ветка отсутствует, её необходимо создать в удалённом репозитории, иначе публикация Allure-отчёта на GitHub Pages не будет работать.
Проверка настроек GitHub Pages
Если workflow pages build and deployment не запустился, необходимо проверить настройки GitHub Pages:
- Откройте вкладку Settings в репозитории.
- Перейдите в раздел Pages → Build and deployment.
- Убедитесь, что параметры соответствуют настройкам на скриншоте ниже.
На этой же странице будет отображаться виджет со ссылкой на опубликованный Allure-отчёт.
Доступ к Allure-отчётам
- Каждый отчёт публикуется на GitHub Pages с уникальным идентификатором workflow, в котором он был сгенерирован.
- Все сгенерированные Allure-отчёты также можно найти в ветке gh-pages.
- Перейдя по ссылке на GitHub Pages, можно открыть сгенерированный Allure-отчёт с историей результатов тестирования.
По итогу, после корректной настройки, при каждом новом запуске тестов Allure-отчёт будет автоматически обновляться и сохранять историю предыдущих прогонов.
Заключение
Все ссылки на код, отчеты и запуски тестов в CI/CD можно найти на моем GitHub: