Как стать автором
Обновить

Использование API-схем для property-based-тестирования

Время на прочтение18 мин
Количество просмотров6.3K
Всего голосов 19: ↑19 и ↓0+19
Комментарии18

Комментарии 18

Замечательная идея.
Благодарю, что нашли время оформить и поделиться.

Schemathesis as a Service. Мы работаем над тем, чтобы всё было в один клик. Просто вводим адрес схемы и всё замечательно тестируется, не надо писать никакого кода.

Первая реакция по ходу чтения - сделать тестер/валидатор доступным как сервис.
Подписался.
Сервис планируется сделать коммерческим или будут другие опции?
Например, я бы заплатил и использовал в своих API.

Спасибо! Рад, что вам понравилась идея! :)

Сервис планируется сделать коммерческим или будут другие опции?Например, я бы заплатил и использовал в своих API.

Бесплатная опция обязательно будет :) В том числе и для Open Source проектов.

НЛО прилетело и опубликовало эту надпись здесь

Скажите прямо - Schemathesis лучше, чем Dredd? :)

На мой предвзятый взгляд - да. С точки зрения поиска дефектов Schemathesis дает намного больше возможностей чем Dredd. В основном за счет использования Property-Based тестирования и генерации данных даже если примеров в схеме нет. Так же Schemathesis полноценно поддерживает Open API 3 (в Dredd поддержка эксперементальная). Конечно, Dredd умеет какие-то вещи которые Schemathesis не умеет. Например поддержка API Blueprint и хуков на разных языках. В FAQ можно почитать немного более развернутое сравнение.

Если говорить о сравнении эффективности на реальных проектах, то можно посмотреть эксперименты одной из статей для ICSE 2022. Сама статья должна быть опубликована в декабре. Эксперименты включают тестирование различных приложений с Open API схемами через Dredd, Schemathesis и еще несколько проектов. Они измеряют покрытие кода в приложениях, ошибки в коде и т.д. Например в одном из экспериментов Schemathesis покрыл 65% кода (в строчках) бэкенда, а Dredd 38%. В некоторых примерах разница меньше, но в целом, в этих экспериментах Schemathesis показал больший или примерно такой же процент покрытия чем Dredd, даже учитывая тот факт, что использовалась довольно старая версия Schemathesis в которой еще не было негативного тестирования.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

Спасибо за тексты ошибок. Скорее всего там под капотом тоже используется валидация JSON Schema, только отдается конкретная ошибка из leaf узла где есть additionalProperties: false. В то время когда в jsonschema отдается ошибка из узла самого близкого к корню (т.е. oneOf). Скорее всего это можно будет решить легче чем я думал изначально, просто выбрав другую ошибку из дерева которое возвращает jsonschema :)

В данный момент Schemathesis использует jsonschema для валидации схем и выводит ошибку оттуда напрямую. Я однозначно хочу это улучшить - для этого есть отдельный issue (довольно долго уже висит). Вполне вероятно что будет проще написать валидацию напрямую чем использовать ошибки из jsonschema (которые особенно неприятно разбирать из-за того древовидной структуры ошибок которую эта библиотека отдаёт).

Я постараюсь вернуться к этой проблеме в ближайшее время.

Отдельно хочу сказать что эту валидацию можно отключить (--validate-schema=false), если нужно запустить тесты без учета валидности схемы.

EDIT: Ответ на комментарий выше

НЛО прилетело и опубликовало эту надпись здесь

Рад слышать! :) Вам спасибо за новый issue и комменты выше - буду благодарен за предложения и любую обратную связь :)

@Stranger6667 Пробую использовать ваш инструмент и время от времени получаю ошибку Hypothesis при чтении создаваемой в тесте сущности:

Falsified on the first call but did not on a subsequent one

Насколько я понял, это присходит, когда Hypothesis делает проверку при двух последовательно идущих одинковых запросах. Он ожидает, что ответ будет одинаковым (проверка идемпотентности), но в теле ответа меняется идентификатор сущности и он считает это ошибкой. Я понимаю, что вопрос не совсем по адресу, но может быть вы подскажете как сделать эту проверку корректно? Или как отключить проверку идемпотентности для этого конкретного теста.

В целом такое происходит из-за того что когда Hypothesis сталкивается с падением теста, он пытается удостовериться что ошибка надежно воспроизводится. Если у него не получается, то он выводит это сообщение об ошибке.

Насколько я понял, это присходит, когда Hypothesis делает проверку при двух последовательно идущих одинковых запросах. Он ожидает, что ответ будет одинаковым (проверка идемпотентности), но в теле ответа меняется идентификатор сущности и он считает это ошибкой

С учетом встроенных проверок, различающиеся идентификаторы не должны вызывать проблему если они оба соответствуют схеме. Но я думаю, что в целом у вас верное понимание ситуации (особенно об идемпотентности)!

Чаще всего такое поведение связано с тем что внутреннее состояние тестируемого сервиса отличается в начале каждого "теста" (это может быть и последовательность запросов, если рассматривать stateful тестирование). Например, если имена сущностей в сервисе должны быть уникальные, то:

  • Запрос на создание сущности с именем Test, пришел 201й ответ несоответствующий схеме, но в сервисе сущность успешно создалась. Hypothesis зарегистрировал ошибку.

  • Hypothesis пытается воспроизвести эту ошибку отправляя такой же запрос как в предыдущем пункте. Получает 409 который уже соответствует схеме - предыдущая ошибка не воспроизводится

 но может быть вы подскажете как сделать эту проверку корректно?

БОльшая часть такого рода проблем решается очисткой состояния приложения перед каждым тестом. В CLI это можно сделать при помощи хука before_call, в Python тестах через использования контекстного менеджера в теле теста (как рекомендуется в документации Hypothesis).

В зависимости от сложности тестируемого сервиса, добавление логики для очистки состояния может сильно замедлить тесты, но результаты будет легче воспроизвести.

Или как отключить проверку идемпотентности для этого конкретного теста

В данный момент, это логику напрямую не отключить, но я планирую сделать такую возможность (возможно даже включить это по-умолчанию)

Надеюсь, что мой комментарий поможет вам :)

Да, дело было в несвоевременной очистке данных. Дмитрий, благодарю и за комментарий и, главное, за сам инструмент. Мне кажется у него прекрасные перспективы.
Я инженер QA, но надеюсь, что не только в своих тестах буду использовать его, но и наших разработчиков сумею приохотить.

@Stranger6667 В документации Schemathesis есть раздел Examples in API schemas в котором написано, что тэг example поддерживается. Т. е. позитивные тесты должны формироваться на основе примеров из схемы. Но у меня они почему-то не используются, хотя в схеме заданы. В результате из схемы автоматически генерируются только негативные тесты, а позитивыне кейсы мне приходится указывать явно в тестах. Пробовал задавать примеры в конкрентом свойстве модели в схеме, но результат тот же. Тесты пишу в pytest. Для "включения" примеров из схемы в тест-кейсы нужно как-то указать это явно? Не могли бы вы показать это на примере?

Возможно это важно: схема использует OpenAPI v. 2.0

В Open API 2.0 ключ "example" используется в меньшем количестве мест, по сравнению с 3.0, а именно в schemaObject и fileSchema. Причем для генерации тестов можно использовать только первый. Schemathesis поддерживает "example" только в таких случаях для Open API 2.0, чтобы следовать спецификации.

Но! Поддерживаются расширения "x-example" и "x-examples" для всех мест где определены соответствующие "example" и "examples" из Open API 3.0.

Т.е. это может выглядеть так (я убрал некоторые части для краткости)

{
  "swagger": "2.0",
  "paths": {
    "/query": {
      "get": {
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "type": "string",
            "x-example": "test"
          }
        ]
      }
    }
  }
}

Документацию обновлю, действительно там не очень явно эта ситуация описана.

P.S. Для более быстрой обратной связи, возможно будет проще использовать мой телеграм

Спасибо, за помощь и дополнительный контакт. Про "x-example" читал и пробовал, но без примера, видимо, делал это неправильно. Теперь заработало. )

Зарегистрируйтесь на Хабре, чтобы оставить комментарий