В этой части мы рассмотрим методы подготовки системы к тестированию и ее восстановления после него, которые обычно называют процедурами установки и демонтажа.
Друзья, поддержите нас вступлением в наш телеграм канал QaRocks. Там много туториалов, задач по автоматизации и книг по QA.
Прочитайте статью “Тестирование API: Postman VS Pytest, часть 1, в которой мы изучаем, как эти инструменты справляются с тестами начального уровня, рассматриваем систему переменных в Postman и рассуждаем о том, насколько Postman подходит в качестве IDE.
Установка/демонтаж в Pytest
Фикстуры
В Pytest у нас есть еще одна замечательная возможность – фикстуры. Фикстуры – это функции, которые фреймворк запускает до и/или после выполнения теста. Это похоже на классическую установку/демонтаж, но модульным способом. Самый простой пример:
def test_add_castle( basic_url, # this is a fixture call new_world_id, # and this is too ): # you can see how basic_url and new_world_id fixtures # are used in the f-string below url = f"{basic_url}/world/{new_world_id}/castle" name = names.get_first_name() response = requests.post(url, json={"name": name}) assert response.ok assert name == response.json()["castle"][0]["name"]
“basic_url” и “new_world_id” – это фикстуры. Они подготавливаются вне теста.
Схема
Представьте, что ваш заказчик – Боузер из вселенной Super Mario Bros. Суперзлодей хочет, чтобы мы создали API для управления подконтрольными ему мирами и замками (app).
Тест методом POST
Давайте вернемся к примеру с POST.
def test_add_castle(basic_url, new_world_id): # assembly url = f'{basic_url}/world/{new_world_id}/castle' name = names.get_first_name() # act response = requests.post(url, json={'name': name}) # assert assert response.ok assert name == response.json()['castle'][0]['name']
Тест можно разделить на три части:
- Этап сборки: подготовка запроса. Здесь на помощь приходят наши фикстуры.
- Действие – мы отправляем запрос.
- Шаг утверждения. Здесь не нужно привлекать дополнительные библиотеки, поскольку Pytest прекрасно обрабатывает обычный оператор утверждения Python.
Как были созданы “Basic_url” и “new_world_id”?
@pytest.fixture # converts any function into a fixture def new_world_id(basic_url): """Adds a new world, returns its ID, and deletes it when testing is complete""" # setup response = requests.post(f"{basic_url}/addworld", json={"name": "whatever"}) worldid = response.json()["world"][0]["id"] # jump into the test yield worldid # teardown requests.delete(f"{basic_url}/world/{worldid}")
Среди многих вещей, фикстуры Pytest – одни из самых удобных. Вы можете легко создать их самостоятельно с помощью декоратора @pytest.fixture.
Основа фикстуры состоит из 3 частей:
- Установка. В нашем примере мы хотим создать новый мир. Поэтому я вызываю POST /addworld и сохраняю его worldid.
- Ключевое слово “yield” в Python обозначает точку, в которой мы переходим к тесту. После выполнения теста будет вызван код, следующий за “yield”.
- Демонтаж: мы удаляем мир, чтобы не засорять нашу БД.
Как вы, вероятно, заметили, “new_world_id” вызывает фикстуру “basic_url”. Это еще одна замечательная возможность: вы можете объединять фикстуры в цепочки.
Вид с высоты птичьего полета.
В этом заключается принципиальное отличие использования фикстур от классической установки/демонтажа. Они позволяют подготовить среду и SUT(System Under Test – тестируемая система) по модульному принципу.
Кстати, Pytest позволяет использовать обычные функции установки/демонтажа.
def setup_module(module): """ setup any state specific to the execution of the given module.""" def teardown_module(module): """ teardown any state that was previously setup with a setup_module method. """
Но нет смысла использовать их вместо фикстур, поскольку последние обеспечивают большую гибкость: вы можете повторно использовать фикстуры в разных тестовых модулях, комбинировать их для разных тестов и т. д.
Установка/демонтаж в Postman
Я знаком со следующими способами, с помощью которых можно произвести установку/демонтаж и, в целом, организовать порядок выполнения скриптов:
Пре-скрипты / тестовые скрипты (коллекция/папка/запрос)
Как я уже упоминал в первой части этого пособия, предварительные скрипты и скрипты тестирования – это встроенный способ подготовки и очистки данных.
Этот подход ограничен:
- Скрипты Postman выполняются в строгом порядке. Вы не можете запустить скрипт уровня запроса перед скриптом уровня коллекции. В Pytest вы можете просто изменить порядок фикстур в списке.
@pytest.fixture def fixture1(): print("I'm Fixture 1 !") @pytest.fixture def fixture2(): print("I'm Fixture 2 !") def test_fixture_order(fixture1, fixture2): # fixture1 goes first ...
def test_fixture_order(fixture2, fixture1): # now fixture 2 goes first ...
- Скрипты Postman запускаются один раз для каждого запроса. Вы не можете сделать так, чтобы скрипт запускался один раз за сеанс или коллекцию. Pytest позволяет вам сделать это без проблем.
@pytest.fixture (scope="function") @pytest.fixture (scope="class") @pytest.fixture (scope="module") @pytest.fixture (scope="package") @pytest.fixture (scope="session")
- Скрипты Postman нельзя пропустить, даже если возникнет такая необходимость. Если вы определите скрипт уровня коллекции, он будет выполняться перед каждым запросом без исключений. И отключить его для конкретного запроса нельзя. В случае с Python вы можете опустить вызов фикстуры в тесте, если это не требуется.
- Скрипты Postman не могут быть изменены на уровне тестов. Представьте, что вы запрашиваете подготовленного пользователя, специально созданного для конкретного случая. И вы хотите, чтобы пользователь зарегистрировался перед выполнением запроса. При этом существует множество способов сделать это с помощью Pytest.
@pytest.fixture def signed_in_user(request): login = request.param.get("login") password = request.param.get("password") return User(login=login, password=password).sign_in() @pytest.mark.parametrize( "signed_in_user", [{"login": "login@mailserver.com", "password": "test_password"}], indirect=True ) def test_indirect(signed_in_user): ...
Поскольку порядок скриптов в Postman строгий, вы не можете легко описывать функции, подобные фикстурам, на уровне коллекции, и передавать параметры настройки для каждого запроса. Вы, конечно, можете использовать одно из описанных выше решений, Postman CLI(Command Line Interface – интерфейс командной строки) или код в переменных, но об этом мы поговорим позже. К сожалению, эти способы гораздо менее удобны, чем родные фикстуры Pytest.
- Вы не можете определить несколько скриптов/фикстур на одном уровне. В Pytest использование всех доступных фикстур для каждого теста не является обязательным.
@pytest.fixture def fixture1(): print("I'm Fixture 1 !") @pytest.fixture def fixture2(): print("I'm Fixture 2 !") def test_single_fixture_used(fixture2): ...
Postman выполняет скрипты каждый раз, когда вы пытаетесь сделать запрос. По этой причине вы вынуждены разбивать коллекции на большее количество папок и распространять шаблонный код по всему проекту. Кстати, Pytest позволяет использовать аналогичное поведение. Просто пометьте фикстуру как autouse=True, и она будет выполняться для каждого теста автоматически.
@pytest.fixture(autouse=True) def fixture3(): print("I'm Fixture 3! And I'm called automatically!") def test_autouse_fixture(): ...
Метод pm.SendRequest
Вы можете отправлять запросы прямо из скриптов Postman. Это позволяет подготовить и/или очистить тестовые данные. Таким образом, вы можете упаковать в скрипт сложную установку/демонтаж, не отвлекаясь на лишние запросы.
pm.sendRequest({ url:'http://some_url', method: 'GET', header: { 'content-type': 'application/json', 'authorization': request.headers["authorization"] }, }, function (err, res){ // do something });
Такие неявные запросы делают ваши тесты неудобными для поддержки и отладки. Иногда это сбивает с толку: вы нажали на кнопку “отправить” всего один раз, но по факту было отправлено несколько запросов. С фикстурами такого бы не произошло.
Запрос
Если у вас нет необходимости в покрытии сложного сценария, можно обойтись дополнительными запросами на установку/демонтаж. Это более наглядно, чем pm.SendRequest, но вы не можете разделять эту установку между коллекциями/каталогами. И если вам нужно выполнить несколько запросов перед фактической проверкой, ваша установка может оказаться слишком длинной и неудобной для работы.
Запросы + метод setNextRequest
Если у вас сложный поток, вы не хотите повторяться и хотите четко видеть все запросы, вам может понадобиться другое решение – поток с SetNextRequest.
Идея проста: используя метод setNextRequest, вы можете изменить порядок запросов.
Таким образом, вы можете имитировать фикстуры, скопированные в сеанс/папку, создавать циклы, повторно использовать код и т. д. В качестве удачного примера я хочу сослаться на статью “My Code Snippets From The London Postman Summit” Пола Фаррелла.
Я перевел пример Пола Фаррелла на нашу “замковую систему”, чтобы показать, с какими трудностями вы можете столкнуться при создании фреймворка, использующего setNextRequest. Вся коллекция находится здесь.
Структура
Один и тот же продукт: 2 конечные точки, представляющие отношения World<->Castle.
Представьте, что у нас есть более сложная задача:
- Проверка добавления замка.
- Проверка редактирования замка.
- Проверка получения одного замка.
- Проверка получения полного списка замков.
Конечно, мы можем использовать подход, описанный выше, но давайте погрузимся в setNextRequest:
Мы можем разделить его на две основные части. Первая – перед каждым хуком.
Это наша установка. Вторая – наши “тесты”, представленные в виде отдельных папок.
Установка
Помните, я упоминал, что вы не можете создавать скрипты с привязкой к сессии? Вы можете только имитировать это. Используя setNextRequest, вы можете направить программу запуска тестов на нужный скрипт, используя переменную-счетчик. Таким образом можно избежать двойного запуска этого скрипта. В данном случае скрипт, скопированный на сессию, используется только для инициализации счетчика. Ниже вы увидите, как счетчик может помочь нам создать набор тестов.
Метод Before each > Add castle
Это наша разновидность “фикстуры для тестирования”. Ее назначение заключается в следующем:
- перезагружать среду Postman перед каждым тестом;
- подготовить тестовые данные (создать новый замок);
- организовать порядок выполнения тестов. Обратите внимание, она не будет запускаться перед каждым запросом, но будет запускаться перед каждой папкой. Мы не можем просто поместить ее в папку предварительного запроса, потому что она будет запускаться каждый раз, когда мы вызываем запрос внутри папки. Но нам нужно, чтобы эта настройка выполнялась только один раз для каждой папки.
Метод Before each — предварительный запрос
В первой строке мы берем текущее значение счетчика. Помните, что в начале он равен 0; мы установили этот показатель в запросе установки, описанном выше.
Мы хотим, чтобы наше окружение было чистым и готовым к новому запуску теста. Поэтому мы вызываем метод clear()…
и сбрасываем счетчик. К сожалению, нет способа очистить все, кроме нужных переменных.
Здесь мы просто инициализировали пару переменных, необходимых для тестов.
Метод Before each — запрос
После скриптов предварительного запроса происходит запрос замка методом POST. Мы добавили новый замок, используя переменные окружения, заданные в предварительном запросе.
Метод Before each — тестовый скрипт
Тестовые скрипты, как известно, выполняются после запросов. В данном случае тестовый скрипт – это еще одна часть оркестровки, а не верификатор.
На первом же шаге мы получаем счетчик. Снова. Любой скрипт в Postman выполняется в отдельной песочнице. Даже пре-/тестовые скрипты одного и того же запроса разделены.
Вы уже видели эту часть кода. Используя setNextRequest, мы можем изменить поток выполнения запросов. Счетчик поможет нам построить нужный поток. На первой итерации мы переходим к “Add Castle > Get All Castles” – первому запросу папки “Add Castle”. На второй – первый запрос второй папки, “Edit Castle”.
Также хочу обратить ваше внимание на то, что переименование запросов может оказаться сложной задачей. Вы можете легко забыть переименовать следующий шаг внутри setNextRequest после переименования запроса. Здесь нет функции “Refactor->Rename”, которая присутствует в той же PyCharm. У вас даже нет возможности искать текст в скриптах прямо из Postman. При масштабном рефакторинге вы, скорее всего, захотите экспортировать коллекцию или использовать другой способ работы с чистым кодом.
В каждом (!) последнем запросе в папке
К сожалению, мы не можем полностью избежать использования шаблонного кода. Для правильной обработки порядка выполнения нам необходимо сделать следующее:
Инкрементируйте счетчик. Как вы помните, вы не можете просто добавить команду ++ в переменную; вам придется прочитать->включить->установить самостоятельно.
Проблемы рабочего процесса метода setNextRequest
Как видите, построение рабочих процессов с помощью setNextRequest не является идеальным:
- Порядок выполнения запросов становится нелинейным и, следовательно, неочевидным.
- Вы не можете выполнять любые запросы по своему усмотрению. Вы должны придерживаться заданной точки входа. Это запутанно и гораздо менее наглядно, чем в Pytest с его фикстурами.
- Запускать тесты нужно только через Postman Runner или Newman.
- Вы не можете быстро рандомизировать порядок выполнения папок с помощью тестов.
- Часто приходится реализовывать дополнительные переменные только для целей оркестровки, например “counter” в примере выше. Вы создаете свой низкоуровневый фреймворк.
- Рефакторинг бывает сложнее, чем ожидалось, и вы можете рассмотреть возможность использования экспорта-импорта коллекций или Postman API.
Если вы используете Pytest, вам не нужно беспокоиться о таких низкоуровневых вещах, как установка порядка выполнения и подсчет запусков тестов. В примере выше мы сделали много шагов для “сбора тестов”. В приведенном выше примере мы предприняли множество шагов для «сбора тестов». Pytest — это не просто программа для запуска тестов. Это также сборщик тестов. Когда вы запускаете его тесты, происходит следующее.
Как мы видим, Pytest более практичен.
Совет: с помощью флага —collect-only вы можете собирать тесты и смотреть, что и в каком порядке можно выполнить без фактического выполнения. Это очень удобно для отладки.
Заключение
Работа с фикстурами похожа на игру с деталями Lego. Вы можете подготовить множество штук разных размеров и форм, а затем использовать те, которые вам нужны, и именно тогда, когда они вам нужны.
Для сравнения, настройка системы тестирования в Postman могла бы быть более очевидной. Вы не можете просто выбрать, какой код хотите запустить до и после запроса/набора запросов. Построить сложный рабочий процесс с установкой/демонтажом сложно без постоянного копирования и вставки кода.
В третьей части мы рассмотрим новую модную функцию Postman – Postman Flows, попробуем хранить и вызывать код прямо из переменных Postman, а также коснемся его шаблонов и встроенных модулей.
Перевод статьи «API Testing Showdown: Postman vs Pytest. Part 2».