Всем привет, меня зовут Александр, в последние 2 года занимаюсь автоматизированным тестирование. Хочу поделиться своим наработанным опытом по созданию API тестов. Для написания автотестов в компании используем selenium webdriver, behave.
Behave — это фрейморк для программирования через поведение системы в python-стиле. Behave использует тесты, написанные на “естественном”, то есть, английском языке.
Selenium webdriver широко используется и думаю в представлении не нуждается, но всегда можно загуглить.
Структура тестов behave очень проста, если feature файлы в которых описываются сценарии и папка steps в которой находятся шаги для выполнения этих сценариев.
Первое на что можно обратить внимание это на структуру feature файлов (в этих файлах находятся сами тесты, которые состоят из различных шагов):
@api @userportal @userportal_api @maps @userportal_maps @userportal_maps_api @requests Feature: Requests userportal maps @get @get_maps_drivers_driverId Scenario Outline: Assert GET request maps/drivers/{driverId} Given get token Then Assert GET request maps drivers/<driver_id> - check <check> Examples: | driver_id | check | |100 |status_code | |[200, 159] |structure | |random 100 |response | |random: 200 |all | @get_maps_factories_factoryId @get Scenario Outline: Assert GET request maps/factories/{factoryId} Given get token Then Assert GET request maps factories/<factoryId> - check <check> Examples: | factoryId | check | | random: 5 |status_code | | random: 5 |structure | |all |response | |all |all |
В этом примере будет выполнено 8 тестов, по 4 на каждый из описанных сценариев.
Для сокращения количества выполняемых тестов, если запрос по всем 3м проверкам выполняется без ошибок, то эти проверки можно закомментировать и будет выполняться только полная проверка запроса (check -> all). Проверки разбиваются для того, чтобы можно было увидеть какие тесты данного метода автоматизированы и для быстрого поиска проблемы, если тест упал.
При создании теста в первую очередь ориентируюсь на метод запроса и название самого метода. Считаю, что хорошее название API теста должно состоять 1) Из метода запроса 2) короткий путь (без base url). Я реализую следующие проверки на текущем проекте: 1) Статус код 2) Структура ответа 3) Сам ответ 4) Фильтры. Можно расширить еще такими: 5) Время выполнения 6) Уровень доступа и тд. (можете добавить свои варианты)
Параметры для шагов сценариев передаются в example.
Для того, чтобы при выполнении каждого теста не происходила авторизация, мною был создан шаг на получение токена, который вносится в контекст и в каждом из шагов и последующих сценариях к нему есть доступ. При запуске тестов токен получаем при выполнении первого сценария, он же обновляется по истечению жизни токена, в данном примере время жизни токена = 30 минутам.
import time import requests from BDDCommon.CommonConfigs.urlconfig import URLCONFIG from BDDCommon.configs import current_settings, STANDS token = None expiration_time = 0 @step('get token') def get_token(context): context.token_auth = get_auth_token() def get_auth_token(): global token global expiration_time current_time = int(time.time()) # Если нет токена или время жизни токена истекло - получаем новый токен if (token is None) or (current_time > expiration_time): company = current_settings['company'] data_login = { 'username': STANDS[current_settings['stand']]['users']['company_users'][company]['main_logist']['login'], 'password': STANDS[current_settings['stand']]['users']['company_users'][company]['main_logist']['pwd']} url = URLCONFIG['base_url_not_os'] + URLCONFIG['login_token'] token = requests.post(url, data=data_login) assert token.status_code == 200, f'Error auth {token.status_code} - {token.text}' token = token.json() token = {'authorization': 'Bearer ' + token['resp']['accessToken']} expiration_time = current_time + 1800 # допустимый срок действия токена 30 минут return token
Сам шаг с проверкой выглядит следующим образом:
@step('Assert GET request maps drivers/{driver_id} - check {check}') def assert_req_maps_drivers_driver_id(context, driver_id, check): # Функция для проверки GET запроса maps/drivers/{driver_id} # driver_id может быть числом, "all", "random ЧИСЛО" # check может быть: "all" "structure" "response", "status_code" ...... driver_id_list = get_all_drivers(company_id=current_settings['company']) # Проходимся по каждому водителю в списке for driver_el in driver_id_list: # Отправляем запрос response = get_req_maps_drivers_by_driver_id(driver_el, context.token_auth) url = f"{URLCONFIG['base_url_not_os']}{URLCONFIG['base_api_url']}" \ f"{URLCONFIG['maps']}{URLCONFIG['drivers']}/{driver_el}" # Запускаем проверки, в зависимости от check if check == 'status_code' or check == 'all': logger.info(f'Check status_code. Method: GET URL: {url}') assert response.status_code == 200, \ f'URL: {url} GET request.status_code Actual: {response.status_code} Expected: 200\n' \ f'Response: {response.text}' if check == 'structure' or check == 'all': logger.info(f'Check structure response. Method: GET URL: {url}') check_structure_maps_drivers_driverId(response.json()['resp'], driver_el) if check == 'response' or check == 'all': logger.info(f'Check response. Method: GET URL: {url}') check_response_maps_drivers_driverId(response.json()['resp'], driver_el) # Вызов ошибки если передали не поддерживающий check if check not in ['all', 'response', 'structure', 'status_code']: raise Exception(f'not valid param check: {check} in function assert_get_req_maps_drivers_driver_id') logger.info(f'assert request maps/drivers/driverId Check: {check} - Completed\n driverIds: {driver_id_list}')
Проверка структуры ответа выглядит следующим образом:
def check_structure_maps_drivers_driverId(resp): assert 'vehicleNumber' in resp assert type(resp['vehicleNumber']) in [str, NoneType]) .........
Проверяем, что все необходимые поля есть в ответе и что их тип соответствует ожидаемому.
Проверка ответа запроса.
def check_response_maps_drivers_driverId(response, driver_id): driver_obj = Driver_obj(driver_id) expected_response = { 'id': driver_obj.id, 'vehicleNumber': driver_obj.vehicle_number, 'fullName': driver_obj.name, .... } assert response == expected_response, f'Error response.\nActual response:\n{response}\nExpected: {expected response}'
Это самый трудоемкий процесс, но и самый интересный. В процессе расширяются методы классов, находятся ошибки в коде, происходит развитие в понимании того, как можно улучшить или ускорить свой код.
Подводя итоги, хотелось бы подчеркнуть главное:
Для более быстрой работы тестов позаботьтесь о том, чтобы не выполнялась авторизация при запуске каждого теста
Запрос отдельно - проверки отдельно
Разбиение проверок на разные функции
Название теста должно отражать название запроса
Вопросы, критику пишите в комментарии, по возможности буду отвечать.
its my first page article
