Перейти к содержимому
DedicatedPHP Контакт

Контрактные тесты для PHP API без нарушения интеграций

Узнайте, как определять и автоматизировать проверяемые контракты, чтобы развивать PHP API без обнаружения несовместимостей после развёртывания изменений.

Редакционная схема PHP API, проверяющей контракты запросов и ответов перед развёртыванием

API определяется не только её PHP-контроллером или опубликованной спецификацией. Её также определяют ожидания, которые уже заложили другие системы: маршрут, имя поля, HTTP-код, формат ошибки или необходимый порядок обхода страниц. Контрактные тесты для PHP API превращают эти ожидания в автоматизированные проверки до слияния кода или его развёртывания.

Цель не в том, чтобы препятствовать любым изменениям. Она состоит в том, чтобы выявить, изменяет ли правка соглашение, наблюдаемое потребителем, и принять осознанное решение: сохранить совместимость, ввести переходный период или версионировать интерфейс. Это особенно важно для внутренних API с несколькими командами, B2B-интеграций и асинхронных потоков, где сбой может проявиться через несколько часов после публикации.

Что решают контрактные тесты и чего они не заменяют

Что решают контрактные тесты и чего они не заменяют — guía visual de DedicatedPHP

Контрактный тест проверяет, что поставщик и потребитель согласны в отношении взаимодействия: при корректном запросе поставщик выдаёт ответ с согласованной структурой, типами и правилами. В обратном направлении потребитель может объявить, какие запросы ему необходимы, а поставщик проверяет, что способен их обслужить.

Этот подход выявляет несовместимости, которые модульные тесты обычно не замечают. Модульный тест может подтвердить, что сериализатор возвращает customer_id; но он не доказывает, что потребитель по-прежнему понимает это поле, если ранее ожидал customerId. Локальный интеграционный тест может покрывать конечную точку API, но не обязательно отражает реальные предположения каждой интеграции.

Они не заменяют другие проверки:

  • Модульные тесты — для доменных правил, валидации и преобразований.
  • Интеграционные тесты — для базы данных, очередей, кэша, аутентификации или подключённых сервисов.
  • End-to-end тесты — для полных критических сценариев в контролируемых средах.
  • Тесты безопасности и производительности — для авторизации, злоупотреблений, раскрытия данных, задержек и ёмкости.
  • Наблюдаемость в рабочей среде — для выявления потребителей, всё ещё использующих выводимое из эксплуатации поведение.

Контракт также не подтверждает, что ответ корректен с точки зрения бизнеса; он подтверждает, что ответ сохраняет заявленные форму и семантику. Поэтому его следует сопровождать примерами, выражающими значимые правила, а не только пустыми схемами.

Что входит в контракт API

Контрактом является любое поведение, которое потребитель может наблюдать и от которого зависит. Если ограничить его JSON успешного ответа, за рамками останутся наиболее частые поломки. Для каждой операции целесообразно согласовать как минимум следующие элементы.

  • Запрос: метод, маршрут, параметры запроса, заголовки, тело, обязательные поля, форматы и ограничения.
  • Ответ: HTTP-код, значимые заголовки, структура, типы, необязательные поля, поля, допускающие null, а также форматы даты, валюты или идентификаторов.
  • Ошибки: коды состояния, тело ошибки, стабильный функциональный код и условия, которые его вызывают. Текст для людей может меняться; код наподобие validation_failed лучше подходит для автоматизации.
  • Пагинация и фильтрация: значение limit, курсора или страницы, стабильность порядка, представление следующего курсора и обработка пустых наборов.
  • Аутентификация и авторизация: допустимый механизм, требуемые заголовки, области доступа или разрешения, а также различие между недействительными учётными данными, отсутствием учётных данных и отказом в доступе.
  • События и webhooks: имя события, версия или схема тела сообщения, подпись, повторные попытки, идентификатор события, негарантированный порядок и ожидания идемпотентности.

Обязательность свойства и допустимость null — независимые правила. Поле может быть обязательным и допускать null, необязательным и не допускать его при наличии либо необязательным и допускать его, если оно присутствует. Аналогично, отсутствующее свойство, свойство со значением null и свойство с пустой строкой — разные состояния. Если потребитель интерпретирует их по-разному, контракт должен это выражать и проверять.

Небольшие изменения, способные сломать потребителей

Изменение может казаться безобидным со стороны поставщика и быть несовместимым с сгенерированным клиентом, строгим валидатором или бизнес-логикой. Например, замена целого числа строкой — 42 на "42" — ломает сравнения и схемы. Превращение поля в необязательное само по себе не определяет, принимает ли оно null: первое правило определяет, должно ли свойство присутствовать, тогда как второе определяет допустимые значения при его включении.

К другим рискованным изменениям относятся возврат 200 там, где ранее возвращался 201, замена пустого списка на null, изменение точности десятичного числа, переименование кода ошибки или изменение порядка пагинации без предупреждения. Добавление поля обычно совместимо для потребителей, допускающих дополнительные поля, но не совместимо, если потребитель валидирует закрытую схему или вычисляет подписи по полному телу.

Совместимость зависит от реального соглашения, а не от отдельного правила. Следует классифицировать каждое изменение с учётом известных потребителей, заявленной потребителями допустимости изменений и критичности потока. Если эта информация неизвестна, это следует считать риском, а не благоприятным предположением.

Выбор между спецификацией, контрактами потребителей или обоими подходами

Спецификация интерфейса, например описание OpenAPI, хорошо работает как общий источник для маршрутов, операций, параметров, схем и ответов. Её можно валидировать в конвейере, чтобы выявлять несовместимые изменения относительно эталонной версии. Это полезно, когда потребителей много или клиенты и документация генерируются из одного определения.

Однако схема не всегда отражает то, что важно каждому потребителю: комбинации фильтров, конкретную ошибку при бизнес-условии или зависимость от значения примера. Контракты, управляемые потребителями, объявляют конкретные взаимодействия, необходимые каждому потребителю. Поставщик проверяет их относительно своей реализации.

Использовать оба уровня часто разумно: спецификация управляет общей поверхностью, а контракты потребителей покрывают высокоценные потоки или семантику, которую трудно свести к схеме. Для каждого артефакта должна существовать чёткая ответственность. Если спецификация не обновляется при изменении кода, она перестаёт быть источником истины и становится фиктивной документацией.

Репрезентативные примеры и граничные случаи

Пример контракта должен содержать реалистичные по структуре данные, но не реальные данные. Для ресурса заказа включите случай с элементами, пустой случай, если он допустим, идентификаторы в согласованном формате и полные даты с часовым поясом, когда это является соглашением. Добавьте случаи отказа в авторизации, неуспешной валидации, отсутствующего ресурса и последней страницы пагинации.

Избегайте фиксации несущественных деталей, которые могут законно меняться, таких как случайный идентификатор, текущее время или порядок JSON-свойств. Используйте точные утверждения для стабильных элементов и явно указывайте допустимые изменения переменных элементов. Каждый пример должен отвечать известной потребности; огромная коллекция выдуманных ответов увеличивает сопровождение, не повышая уверенности.

Постепенное внедрение в существующем PHP API

Необязательно моделировать весь API до получения пользы. Начните с инвентаризации потребителей: внутренних приложений, B2B-клиентов, пакетных процессов, мобильных приложений, автоматизаций и получателей webhooks. Зафиксируйте владельца, канал связи, используемую операцию, критичность и возможность обновления.

Затем определите приоритет конечных точек API, которые создают или изменяют ресурсы, аутентифицируют пользователей, обеспечивают финансовые процессы или запускают автоматизации. Установите базовое описание их текущего поведения с помощью проверенной спецификации и тестов против воспроизводимого экземпляра API. В PHP тест должен задействовать реальный HTTP-слой приложения, а не напрямую вызывать класс сервиса: контракт включает маршрутизацию, промежуточные обработчики, сериализацию и обработку исключений.

POST /api/orders
Authorization: Bearer token
Content-Type: application/json

{"items":[{"sku":"ABC-1","quantity":2}]}

201 Created
{"id":"ord_123","status":"pending","items":[...]}

Предыдущий пример полезен только в сопровождении правилами: какие поля обязательны, всегда ли id является строкой, какие ошибки возвращает недействительный SKU и гарантировано ли начальное состояние. Именно эти правила следует превратить в утверждения.

Валидация в непрерывной интеграции и перед развёртыванием

Конвейер должен завершаться ошибкой до слияния, если реализация нарушает утверждённые контракты. Практический поток включает запуск модульных тестов, поднятие контролируемых зависимостей, запуск PHP API с тестовой конфигурацией и валидацию спецификации, контрактов поставщика и контрактов репрезентативных потребителей. Тесты должны использовать изолированные и детерминированные данные, чтобы сбой был воспроизводимым.

В запросе на изменение дополнительно сравнивайте предложенную спецификацию с опубликованной версией, чтобы отмечать удаления маршрутов, ужесточение требований к наличию, изменения в допустимости null, изменения типов и удалённые ответы. Диагностика должна указывать операцию, взаимодействие и нарушенное правило; простая ошибка схемы вынуждает слишком много исследовать.

Перед развёртыванием запускайте тот же набор тестов на артефакте, который будет опубликован, а не на другой сборке. После развёртывания отслеживайте коды ошибок, ошибки десериализации, о которых сообщают клиенты, использование версий и трафик к устаревшим маршрутам. Предварительная валидация снижает риск, но не заменяет подтверждение поведения под реальным трафиком.

Совместимость, устаревание и безопасное удаление

Совместимость, устаревание и безопасное удаление — guía visual de DedicatedPHP

Когда изменение несовместимо, предпочтителен явный переходный период. Можно добавить новое поле, сохранив прежнее, ввести новую операцию или версию и сообщить дату удаления, подкреплённую сигналами использования. Устаревание — это операционный период с владельцами, коммуникацией и измерением, а не только примечание в документации.

Не удаляйте поведение только потому, что истёк срок, если вы не можете выявить потребителей, которые ещё используют прежнее поведение, или если поток критичен. Когда это возможно, в контролируемом режиме предоставляйте предупреждения и метрики, чтобы обнаруживать старое использование без изменения ответа. Постепенный выпуск новой версии позволяет наблюдать ошибки и исправлять контракты до расширения изменения.

Наиболее распространённые ошибки — тестировать только успешные ответы, моделировать сообщения для людей вместо кодов ошибок, считать, что все клиенты игнорируют новые поля, и не привлекать реальных или репрезентативных потребителей. Контрактные тесты приносят пользу, когда отражают соглашения, поддерживаемые обеими сторонами, и выполняются как обычное условие поставки.

Хотите применить эти идеи в своем проекте?Давайте обсудим вашу PHP-платформу.
Посмотреть связанные услуги