Протестировать позитивные сценарии использования API можно легко и быстро, используя JSON схему. В этой статье мы поговорим о полезных приемах, хитростях, которые можно применить для ускорения процесса, и об источниках данных для такого тестирования. Излагать буду “для самых маленьких” - если хотите быстро подтянуть JSON схемы в свой проект и готовых онлайн-генераторов вам мало, но вы не планируете погрязнуть в чтении документации, добро пожаловать. Примеры из этой статьи я выложил в Gitlab (готовились они под конференцию PiterPy, поэтому и название проекта соответствующее): https://gitlab.com/bp3_niva/piterpy-2023-sample  Всем привет! В узких кругах я широко известен как Дядя Вова. В этом году исполнилось 17 лет, как я работаю в тестировании, и 7 из них я в Максилекте - занимаюсь автоматизацией тестирования. Текущая моя роль - лид, но в душе я остался экспертом по бэкенд тестированию. Тестирование - всегда узкое место разработки в том числе потому, что оно всегда догоняет. Поэтому в своих статьях я рассказываю о том, как что-то можно сделать максимально быстро. В прошлый раз я рассказывал о том, как использовать параметризацию тестов. Сегодня поговорим про использование JSON схемы в регрессе (т.е. фактически в smoke-тестировании). Согласно легенде, smoke-тестирование пошло из разработки электроники. Собранный электроприбор быстро включают и выключают, чтобы проверить, пошел ли из него дым. Если дым не пошел - smoke-тест пройден. Просто вся электроника работает на магическом дыму. Если дым вышел - она больше не может работать. В разработке smoke-тесты позволяют понять, что основные фичи решения хоть в каком-то объеме работают. Это частный (очень лайтовый) случай регресса. Smoke-тест не говорит о том, что конкретно сломалось, а демонстрирует существование проблемы. При этом если тест проходит (зеленый), можно говорить об определенном уровне качества продукта - значит при внесении изменений мы ничего не сломали. Я занимаюсь бэкенд тестированием, т.е. API - это мое все. JSON-схему я применяю непосредственно при проверке соответствия каждого ответа API ожидаемому результату. Но спектр возможных применения JSON-схемы этим не ограничивается. Фактически, любой JSON можно валидировать таким образом, откуда бы он не пришел - это могут быть сообщения из Kafka / Rabbit или даже поля базы. Зачастую написать схему намного быстрее, чем тянуть откуда-то данные для сравнения. К сожалению, онлайн-генераторы схем, которыми пестрит интернет, делают спорные вещи. Поэтому я хочу рассказать, как быстро подготовить JSON-схему своими руками, не используя сторонние инструменты. Очень люблю Python и PyTest - так что свой рассказ построю на них.  Рассказывать буду на максимально упрощенных примерах. Их исходники вы можете найти на GitLab: https://gitlab.com/bp3_niva/piterpy-2023-sample Переключаясь между ветками, вы найдете все упомянутые разделы. В реальной жизни в ходе тестирования мы взаимодействуем с неким API, который и отвечает с помощью JSON. Но для описания в рамках этой статьи нам не нужно все, что у сервера под капотом. Нужен только JSON, который он присылает. Поэтому мы и будем с ним работать - как со строкой или с файлом.  Предположим, в JSON содержится информация о неком пользователе - с идентификатором, ФИО, полом, датой рождения, телефоном и т.п. И пусть Result - это поле, которое говорит о том, что карточка пользователя была корректно обработана. На данный момент я подобрал пример таким образом, чтобы использовать именно примитивные типы данных. Далее в повествовании мы пойдем от простого к сложному - будем создавать все более хитрые схемы с нетривиальными проверками. Возьмем драфт примитивной схемы: И возьмем тест:   Надо отметить, что validate в данном случае работает не со строками JSON и схемы. Он скастован именно к dictionary из Python. Поэтому в JSON выше мы видим None и True (с большой буквы), хотя фактически в ответе API будет null и true (с маленькой буквы). В таком виде тест работает без ошибок, потому что схема пока пуста. В ответе API в JSON могут быть различные примитивные типы - boolean, integer, number (немного непривычно, что так здесь назван float, но учитывайте, что между Python и JSON все-таки есть разница).  В нашей схеме мы можем сказать, что ID - целое число (integer), ФИО, пол, дата рождения, телефон, а также result - строка (string), паспорт - null (в терминах JSON), рост - не целочисленный тип (number), а гражданство - boolean. Здесь мы видим, что вся схема имеет “type”: “object”. К этому вернемся чуть позже. Тест на такой схеме работает без ошибок. Но если, например взять в нашем ответе API в кавычки значение роста (чтобы оно также стало строкой), тест упадет с ошибкой - “1.76” не является типом number. И так можно провалидировать любое поле. Как правило, JSON схема реагирует на первую проблему и не сообщает об остальных (если в JSON таких проблем несколько). О следующих проблемах приходится узнавать через последовательную валидацию. На практике в большинстве случаев, исправляя первую проблему, вы сами обнаружите и остальные. А если нет - итерации в любом случае их подсветят. Далее поговорим про более расширенную валидацию. Нам важно, чтобы Result всегда был Ok. Это можно прописать в схеме: Если при этом от API придет ответ “OOK”, тест упадет с ошибкой. Аналогично можно задать варианты ответа через перечисление enum. Это легко продемонстрировать на примере пола - “М” или ”Ж”: До введения этой валидации пол мог быть любым, а сейчас тест пройдет без ошибок, только если пол М или Ж. Зададим границы для ID - пусть он не может быть равен нулю (т.е. зададим ему минимальное значение, равное 1). Если в ответе API придет ID=0, тест упадет с ошибкой. С максимальными значениями можно работать аналогично. В схеме можно также задавать exclusiveMinimum и exclusiveMaximum. Это вариации minimum и maximum, соответственно, которые не включают указанные значения, т.е. работают как больше / меньше (а не “больше или равно” / “меньше или равно”). Еще одна удобная штука - ограничение длины. Мы можем указать, что телефонный номер должен иметь 12 символов (считая “+”).  А еще можно задать паттерн с помощью регулярного выражения. Допустим, для даты можно указать следующие условия: Если при этом в ответе от API в результате какой-то ошибки придет 16 месяц, тест упадет. Отмечу, что закрывать проверку даты регулярным выражением - не самая хорошая практика. Так будет не учесть високосный год. Но в данном конкретном случае регулярное выражение помогает отследить изменение формата данных - если, допустим, сначала придет дата, а потом уже год. На самом деле JSON - это объект с определенными полями, и схема позволяет проверить наличие или отсутствие этих полей. Предположим, в ответе мы потеряли поле с датой. В текущем виде объект останется валидным. Чтобы задать обязательные поля, используется настройка required. В ней мы перечисляем, какие именно поля должны присутствовать у валидного объекта: Еще одна интересная настройка - additional properties, которая отвечает за то, могут ли у объекта появляться новые поля. По умолчанию она true - это означает, что дополнительные поля допустимы. Если значение этого свойства исправить на false, JSON-схема дополнительные поля не пропустит. Любопытно, что на текущем проекте по просьбе разработчиков additional properties установлено в true - здесь я при изменении списка свойств актуализирую тесты в рабочем порядке после того, как мне поставят задачу (true обеспечивает прохождение их билдов, пока задача в работе). Но я сталкивался и с противоположными историями. На одном из проектов это свойство было установлено в false, поскольку разработчикам так больше нравилось. Они хотели, чтобы при изменении свойств тесты становились красными. Так что отчасти это разговор о вкусовщине. Но чтобы не путаться, я рекомендую указывать additional properties явно, даже если оно соответствует дефолтному значению. Предположим, у нас есть функция, которая возвращает список сущностей. Тестовый объект будет элементом списка. JSON-схема позволяет работать с минимальным и максимальным количеством item-ов в списке (minItems и maxItems).  Вы вряд ли ожидаете, что список вернет 0 объектов, поэтому minItems можно установить на 1. А maxItems позволяет валидировать дефолтные значения количества объектов на странице, если например тестируется API с пагинацией. Обычно списковые API по дефолту отдают 20 item-ов, которые на фронтенде попадают на одну страницу. Когда вы нажимаете кнопку следующей страницы, API выдает еще 20 элементов. Таким образом, maxItems можно установить на 20 - и в этом случае вы будете уверены, что пагинация работает корректно (что разработчик, пришедший с другого проекта, не поправил это значение на более привычное для него 10 или, скажем, 50). Вернемся к примеру. Здесь теперь задан тип array, внутри которого - item-ы, о которых мы говорили ранее. Minitems я задал 1, потому что ожидается, что массив не должен быть пустым. А чтобы пояснить, как работают другие свойства, добавил поле driver_license и задал категорию B: Обратите внимание, что новое поле driver_license не указано в списке обязательных, т.е. все объекты, которые мы упоминали до этого (и в которых не было этого поля) останутся валидными. Здесь я указал, что это массив, элементы которого могут быть строками из списка - "A", "B", "C", "D", "E" - причем, обязательно уникальными (uniqueItems). Это значит, что тест упадет, если мы укажем категорию Q или если случайно введем BB. Я не зря начал свой рассказ с того, что JSON-схемы мы будем создавать без использования онлайн-генераторов, потому что обычно они спотыкаются о ситуации,  когда тип одного и того же поля варьируется от записи к записи. Простой пример. У некоторых людей, в том числе в России, отчество не предусмотрено по паспорту, поэтому данное поле мы не можем сделать обязательным. А если все-таки зафиксируем его существование, то в это поле может попасть как реальная строка, так и null. Если отдать такой список онлайн-генератору, он создаст схему по первому встретившемуся типу. Так в surname - попадет тип string. Обычно в онлайн-генераторах есть свои валидаторы. И данный список проверку не пройдет, потому что во втором элементе списка значение отчества - null. Получаем абсурдную ситуацию - генератор не может валидировать схему, которую сам же сделал. Создавая схему самостоятельно, тип можно задать через список ["string", "null"]: В этом случае список пройдет проверку. У онлайн-генераторов есть и другая проблема - они включают в схему слишком большое количество избыточной информации, которая будет мешать ее обслуживать. Поэтому я настаиваю на том, чтобы писать JSON-схемы руками. Их легче читать и дорабатывать, да и выглядят они опрятнее. Еще одна альтернатива, о которой хотел поговорить в этом примере, - ID. Предположим, ID в ответе может быть как числовым, так и строчным вида ID002. Т.е. оно либо принимает значение integer с минимальной 1, либо является строкой с определенным паттерном. Эти условия можно задать через any of, указав все возможные варианты: Допустим в ответе API приходит несколько дат - помимо даты рождения у пользователя есть еще какая-нибудь дата активации. Я придерживаюсь принципа, что если ты что-то пишешь два раза, значит при этом делаешь что-то не так.  Для проверки даты выше мы использовали довольно сложное регулярное выражение: ^(19[789]|20[012])\\d-(0\\d|1[0-2])-([0-2]\\d|3[01])$. Этот паттерн можно вынести в отдельное место, чтобы переиспользовать везде, где требуется. Для этого нужно создать в схеме раздел defs: Далее в схеме везде, где приходит дата, мы используем ссылку на formatted_date: Такой подход позволяет при необходимости менять регулярку только в одном месте - это может быть нужно, если разработчики еще не определились, какой формат даты использовать, и периодически его меняют. Но при этом рекомендую помнить о том, что JSON-схема всегда показывает только первую проблему. Это значит, что по падению теста будет непонятно, что именно испортилось - ответ API или, допустим, регулярное выражение (т.е. неверен формат всех дат). Тема схем очень обширная и каждый может найти в ней то, что ему больше подходит. Это инструмент, который позволяет очень быстро получить средство для проверки большинства эндпоинтов. Если в проекте нет сложных цепей зависимостей, когда для создания одной сущности нужно понасоздавать много всего вокруг, то JSON-схемы помогут за пару рабочих дней покрыть практически весь проект. Если у вас есть вопросы, задавайте в комментариях. Автор статьи: Владимир Васяев, Максилект. Статья написана по мотивам выступления специалиста на конференции PiterPy 2023. P.S. Мы публикуем наши статьи на нескольких площадках Рунета. Подписывайтесь на нашу страницу в VK или на Telegram-канал, чтобы узнавать обо всех публикациях и других новостях компании Maxilect.    Глас компании Maxilect