Доброго времени суток! В этой статье я собираюсь продолжить рассказ о своем небольшом опыте автоматизации. В прошлой статье я показал, как это сделать с помощью Postman — сегодня покажу, как это реализовать, используя язык программирования Python, фреймворк Pytest, библиотеку Requests.
Для начала представлю дерево проекта.
Далее о вспомогательных файлах: helpers.py, configKey.py.
В helpers.py генерируются три токена проекта: основной и два интегрирующих сервиса, а также используется метод валидации схем ответов.
import pytest from jsonschema import validate as validate_json, ValidationError import requests as r from configKey import * import time today = time.strftime("%Y-%m-%d", time.localtime()) def validate_schema(response, schema): try: validate_json(response, schema) except ValidationError as e: print(e) return False return True def update_headers(): """Первичная проверка на доступ сервера""" try: response = r.post(**_url, json=**_person_data, verify=False) **_headers['Authorization'] = response.text response = r.get(url=***_url, headers=***_headers, verify=False) ***_headers['Authorization'] = 'Bearer ' + response.json()['****'] response = r.get(url=****_url, headers=**_headers, verify=False) ****_headers['Authorization'] = 'Bearer ' + response.json()['*****'] except: return False return True
Принцип генерации токена следующий — отправляется запрос на сервер, из ответа извлекается непосредственно токен, он вводится в отдельную и переменную и переиспользуется в тестах при импорте helpers.py. Самое главное — два импорта: Pytest, requests — первый для запуска тестов, второй для использования библиотеки, которая будет отправлять запросы.
В configKey основные базовые урлы и схемы хедеров.
Непосредственно к тестам. Сервис ЕМИАС. Необходимо выполнить следующие импорты.
import pytest import requests as r from configKey import * from schemas.schemas_valid import * from helpers import *
Далее вводим класс, в котором будут переменные, методы и тесты. Самое главное — название класса должна начинаться на «Test».
class Test***: """Классовые переменные с динамическими данными""" visit_id_home = '' """Айди вызова врача на дом""" visit_id = '' """Айди записи к врачу""" available_time = '' """Талон к врачу""" @classmethod def setup_class(cls): """Принудительное обновление токенов""" if not update_headers(): pytest.exit('Ошибка получения токенов', 3) @classmethod def teardown_class(cls): """Удаление данных""" update_headers() if Test***.visit_id_home: r.delete(f'{link}/*******/{Test***.visit_id_home}', headers=***_headers) if Test***.visit_id: r.delete(f'{link}/*****/{Test***.visit_id}', headers=***_headers)
На 7-ой и 13-ой строках я ввожу классовые методы: setup_class(cls), teardown_class(cls) — для того, чтобы при запуске любого теста в этом классе данные методы обязательно вызывались. setup_class(cls) — обновление токенов, которые генерируются в helpers.py перед запуском, teardown_class(cls) — удаление данных после запуска. Ps: в правилах хорошего тона очищать за собой пространство от тестовых данных во избежание дубликатов или отсутствия возможности записаться, так как активная запись может быть только одна — повалятся ошибки 400 «Bad request».
Флоу пользователя я описывал в прошлой статье, можете ознакомиться там) — это статья, как дополнение к предыдущей.
Непосредственно к тестам. Название тестов должны начинаться с «test», чтобы pytest мог их идентифицировать. Не забываем соблюдать табуляцию — наши тесты находятся внутри класса, поэтому классовые методы и тесты на один Tab вправо.
Перед тестом я использую два метода для парсинга тел ответов.
@classmethod def parse_doctor_data(cls, response): items = response.json()['items'][0] for doctor in items['doctors']: for sched in doctor['schedule']: if sched['count_tickets'] > 0: print(sched['date']) return { 'available_date' : sched['date'][0:10], 'doctor_id' : doctor['id'], 'lpu_code' : doctor['lpu_code'], } return {} @staticmethod def parse_ticket_time_data(response): schedule = response.json()['schedule'] for sched in schedule: if not sched['busy']: return sched['time'] return ''
На 3-ей строке я ввожу переменную items, где меня интересует первый открытый день для записи. На 4-ой строке я запускаю цикл, который проходит по каждому доступному врачу. Далее – на 5-ой строке я запускаю вложенный цикл, в котором хочу получить все расписания свободных дней для записи. На 6-ой строке я уточняю, есть ли наличие свободных талонов, если все условия выполняются, то я прошу записать данные в переменные глобального окружения. Вложенный цикл используется потому, что массив тела ответа состоит из нескольких слоев.
def test_visit_to_doctor(self): """Получение свободных талонов к врачу""" url = f'{link}/*********' response = r.get(url, headers=***_headers) assert response.status_code == 200, f'Ожидался статус код 200 ОК, но получен {response.status_code}' assert validate_schema(response.text, test_visit_to_doctor_1), 'Json schema does not match' doctor_data = TestEmias.parse_doctor_data(response) """Получение талонов к врачу по конкретному дню.""" assert doctor_data['available_date'] and doctor_data['doctor_id'], "Данные о враче не были установлены." url = f"{link}/*****/{doctor_data['doctor_id']}?*****={doctor_data['available_date']}" response = r.get(url, headers=***_headers) assert response.status_code == 200, f'Ожидался статус код ОК, но получен {response.status_code}' assert validate_schema(response.text, test_visit_to_doctor_2), 'Json schema does not match' Test***.available_time = Test***.parse_ticket_time_data(response) """Запись на прием к врачу.""" assert TestEmias.available_time, "Время талона не было установлено." url = f'{link}/*****' payload = { "doctor_id": doctor_data['doctor_id'], "email": "***@yandex.ru", "lpu_code": doctor_data['lpu_code'], "day": doctor_data['available_date'], "time": Test***.available_time } response = r.post(url=url, headers=***_headers, json=payload) assert response.status_code == 200, f'Ожидался статус код ОК, но получен {response.status_code}' assert validate_schema(response.text, test_visit_to_doctor_3), 'Json schema does not match' Test***.visit_id = response.json()['entry_id'] """Отмена записи к врачу""" assert Test***.visit_id, 'Не высталвлен visit_id' response = r.delete(url=url + f'/{Test***.visit_id}', headers=***_headers) assert response.status_code == 204, f'Ожидался статус-код ОК, но получен {response.status_code}' assert validate_schema(response.text, test_visit_to_doctor_4), 'Json schema does not match' Test***.visit_id = ''
Для того, чтобы отправить запрос в рамках теста нам нужно:
1) Обозначить тест, что и сделано в первой строке;
2) Ввести обязательные параметры запроса: url, headers, body (post, put, patch — запросы).
2.1) URL — указываем адрес, в моем примере я вызываю {link} , потому что это базовый урл, который лежит в helpers.py, а под звездочками — ендпоинты, если будете указывать прямую ссылку, то это будет выглядеть примерно так: url = ‘https://habr.com/ru/article/new/’.
2.2) Headers — т.к. у меня хедеры тянутся из helpers.py, я их импортирую, поэтому я их не ввожу.
2.3*) Если у Вас Post, Put или Patch запрос, то добавьте строчку ‘payload’ или назовите по-своему.
3) Отправка запроса — т.к. я импортировал библиотеку «Response as r» мои запросы выглядят следующим образом (три обязательных аргумента, если запрос Post):
response = r.post(url=url, headers=***_headers, json=payload)
4) Проверка — это может быть проверка на статус-код, на наличие конкретного ключа в ответе, на время ответа или проверка схемы — проверки используются индивидуально, но на статус-код проверяем почти всегда.
assert response.status_code == 200, f'Ожидался статус код ОК, но получен {response.status_code}'
Конструкция assert работает следующим образом (для тех, кто не в курсе): если возвращается 200 статус-код, то все окей, а если возвращается другой статус код, например, 403, то тогда срабатывает assert и возвращает вам код ошибки — исходя из второй части верхней проверки "{response.status_code}'{response.status_code}".
5*) Распечатывать ответ или его часть. Например: print(response.text)
Можно распечатать URL, новую переменную или тело запроса для дебага, но обычно для этого используют pdb (python debug), но можно и прямо в тестах, но локально)
6*) В моем случае мне необходимо записать тело ответа в переменную класса для ее последующего использования.
Test***.visit_id = response.json()['entry_id']
Под звездами — название класса, визит_айди — название новой переменной (не забудьте ее предварительно указать в начале класса) = конкретный ключ из ответа запишется в переменную.
6*) Схемы валидации. Обсудите с командой, нужны ли Вам такие проверки. В отдельном файле «schemas_valid.py». Они выглядят примерно так:
test_doctor_visit_home_schema_3 = { "code": 200, "message": "Вызов отменен", "message_code": "2000" } test_banners_schema = { "required": [ "content", "pageable", "totalPages", "totalElements", "last", "number", "size", "sort", "numberOfElements", "first", "empty" ] } test_sessionId_schema = { "sessionId": str, "required": ["sessionId"] }
7) Запуск. Советую запускать из терминала IDE или системного терминала по следующей команде: pytest -s -v -k "название теста"
Либо запустить целиком файл.
Пишем: Pytest + вставляем из буфера обмена относительный путь + «-s -v»
pytest tests/smoke_test/high/test_003.py -s -v
8) Убедитесь, что файлы с тестами лежат в папке «test» и названия файлов, классов и тестов начинаются на ‘test’. Проверьте, чтобы в ‘requirements.txt’ были следующие зависимости:
jsonschema==4.22.0 pytest==7.4.4 Requests==2.32.3
Чтобы Вам не мешали лишние библиотеки — удалите файл ‘requirements.txt’ В ДАННОМ ПРОЕКТЕ и выполните команду: pip3 install pipreqs
Спасибо, что прочел до конца, надеюсь Вам поможет эта статья)
ссылка на оригинал статьи https://habr.com/ru/articles/841562/
Добавить комментарий