Удаленка для jenkins+selenide+selenoid+allure+docker спецов на 2-3 часа в день. 100% remote! Присоединиться к проекту

Как правильно хранить данные (request, response)

test-data
api
python
Теги: #<Tag:0x00007fedbb35b1e0> #<Tag:0x00007fedbb35b0a0> #<Tag:0x00007fedbb35af60>

(Viktor Kliui) #1

Здравствуйте ув. форумчане. Я знаю что этот вопрос задавался тысячу плюс один раз, но, я так и не нашел для себя решения. Сейчас пишу тесты для API, в тестах мне нужны данные для генерации request, а так же для сравнения полученного response с валидными данными. Я должен запускать тесты от 4-х разных пользователей, в которых разные права в приложении и в следствии, они получают разные респонсы на один и тот же запрос.

Пример теста для одного пользователя:

    def test_add_route(self, make_request, clear_result):
        url = URL.fixed_routes
        # Подготавливаем данные в JSON для запроса
        data = _.get_JSON_request('add_route', **{"internalNumber": "1111",
                                                  "externalNumber": "0666816657"})
        # Делаем запрос и получаем ответ
        response = make_request(url, data)
        # Получаем id с респонса, для формирования ответа
        route_id = response.json()['id']
        # Формируем валидный ответ
        answer = _.get_JSON_response('add_route', **{'id': route_id,
                                                     "internalNumber": "1111",
                                                     "externalNumber": "0666816657"})
        clear_result['url'], clear_result['id'] = url, route_id
        assert response.status_code == 200
        assert answer == response.json()

Сейчас тесты поменялись и исполняются от 4-х юзеров:

@pytest.fixture(scope='session')
def get_role():
    #Получаем роль из параметров теста из jenkins
    try:
        role_name_from_jenkins = os.environ['role_for_test']
    # Если не передали используем рут роль
    except KeyError:
        role_name_from_jenkins = 'ROOT'
    roles = {'ROOT' : "Basic Q231Q6QVBJX2F1dG90ZXN0X1JPT1Q=",
            'ADMINISTRATOR' : "Basic Q123NUUkFUT1I6QVBJX2F1dG90ZXN0X0FETUlOSVNUUkFUT1I=",
            'USER' : "Basic QV1230ZXN0X1VTRVI=",
            'SUPERVISOR' : "Basic QVBJX2F1dG90ZXN0X1NVUEVS124VklTT1I="}
    auth = roles[role_name_from_jenkins]
    headers = {
        'content-type': "application/json;charset=UTF-8",
        'authorization': auth}
    data = {'headers': headers, 'role' : role_name_from_jenkins}
    return data

add_route_users_data = {'ROOT' : {'request':{"internalNumber": "1111","externalNumber": "0666816657"},
                   'response': {"internalNumber": "1111", "externalNumber": "0666816657"},
                   'status_code':200},
         'USER':{'request':{"internalNumber": "1111","externalNumber": "0666816657"},
                   'response': {'SCB_ROUTE_CREATE_EXCEPTION': 'User have no permissions'},
                   'status_code':403},
         'ADMINISTRATOR' : {'request':{"internalNumber": "1111","externalNumber": "0666816657"},
                   'response': {"internalNumber": "1111", "externalNumber": "0666816657"},
                   'status_code':200}}

def test_add_route(make_request, clear_result, get_role):
    url = URL.fixed_routes
    role = get_role['role']
    # Подготавливаем данные в JSON для запроса
    data = _.get_JSON_request('add_route', **add_route_users_data[role]['request'])
    # Делаем запрос и получаем ответ
    response = make_request(url, data)
    # Получаем id с респонса, для формирования ответа
    route_id = response.json()['id']
    # Формируем валидный ответ
    answer = _.get_JSON_response('add_route', **add_route_users_data[role]['response'])
    clear_result['url'], clear_result['id'] = url, route_id
    assert response.status_code == add_route_users_data[role]['status_code']
    assert answer == response.json()

Вопрос собственно в чем, мне данные как в переменной “add_route_users_data”, нужны будут для каждого теста, как можно их хранить или мой подход вообще не верный?


(Bolatbek) #2

Посмотрите в сторону параметризации pytest`а:
https://docs.pytest.org/en/latest/parametrize.html


(Viktor Kliui) #3

Та мне не проблема подставить и т.д. У меня вопрос по поводу хранения, каждый тест должен иметь вот такую инфу:

add_route_users_data = {'ROOT' : {'request':{"internalNumber": "1111","externalNumber": "0666816657"},
                   'response': {"internalNumber": "1111", "externalNumber": "0666816657"},
                   'status_code':200},
         'USER':{'request':{"internalNumber": "1111","externalNumber": "0666816657"},
                   'response': {'SCB_ROUTE_CREATE_EXCEPTION': 'User have no permissions'},
                   'status_code':403},
         'ADMINISTRATOR' : {'request':{"internalNumber": "1111","externalNumber": "0666816657"},
                   'response': {"internalNumber": "1111", "externalNumber": "0666816657"},
                   'status_code':200}}

Создавать файл чисто с такими данными или использовать какую нибудь хранилку. Интересно кто как это делает.

Это не параметры для теста, это грубо говоря 4 разных теста, от разных пользователей, просто что бы не плодить много букоф, хочу сделать таким образом


(Bolatbek) #4

А, ну тут разные варианты. От хранения в БД, до обычных csv/json и прочих форматов файлов.
Предпочитаю обычный json.


(Viktor Kliui) #5

Вот у меня такая структура проекта:

Может лучше создать такую же структуру с классами и т.д. И просто в .py файлах хранить данные? Просто хочу услышать все ЗА и ПРОТИВ.


(Zhenya Karpovich) #6

в .py файлах хранить данные как-то сильно не кошерно. Послушайте предложение выше и посмотрие в сторону csv/json ну или на крайний случай в текстовых файлах


(Igor Balagurov) #7

Хранить в json - можно, но по мне это какой-то overhead, сейчас выглядит вполне нормально по крайней мере пока не разрослось.

Можно ещё как вариант параметризовать можно саму фикстуру и она сама будет выдавать нужные значения:
https://docs.pytest.org/en/latest/fixture.html#parametrizing-fixtures

Если же данные могут отличаться(окружения, например), будут меняться, то можно их вообще не хранить, а получать в прекондишинах (сходить на какой-нибудь API point и один раз получить все необходимые роли), если будет расширение, изменение в ролях - будет в таком случае проще поддерживать.


(rmerkushin) #8

— Граждане! Храните деньги в сберегательной кассе! Если, конечно, они у вас есть.

А если серьезно, то используйте для response - JSON Schema.


Если не планируете засылать в сервис корявые и не валидные json, тогда можете воспользоваться еще и такой магией:


или


(Maxim Andryushchenkov) #9

Я обычно в CSV загоняю, так как на гитхабе есть удобный просмотр данных да и парсить легко


(Viktor Kliui) #10

Спасибо большое, не слышал про json schema, то что мне не хватало при проектировании структуры тестов в целом, написал был для этого свой “мини-велосипед”, искал ключ и подставлял значение для реквестов и респонсов:

 def generate_JSON(JSON_request, kwargs):

        for i in kwargs.items():
            # Проверка есть ли нужное значение в основном теле запроса
            if i[0] in JSON_request:
                JSON_request[i[0]] = kwargs[i[0]]
            # Если не в основном, ищем во вложенных списка/словарях
            else:
                # Ищем значение во вложенных словарях
                for j in JSON_request:
                    # Проверяем, если ответ идет в списке
                    if type(JSON_request[j]) == list:
                        JSON_generator.generate_JSON(JSON_request[j][0], {i[0]: i[1]})
                    # Ищем вложенные словари
                    if type(JSON_request[j]) == dict:
                        # Проделываем все то же с вложенным словарем
                        JSON_generator.generate_JSON(JSON_request[j], {i[0]: i[1]})
        return JSON_request

(rmerkushin) #11

Это вы так делали шаблон и подставляли в него значения? Чет не въехал в систему нипель )

Если да, то лучше как-то так:

replace = {'$name': 'john', '$age': '28'}
template = '{"name": "$name", "age": $age}'

for key, val in iter(replace.items()):
    template = template.replace(key, val)

print(template)

(Viktor Kliui) #12

да, подставлял, просто если есть словари со вложенными словарями или обьектами, тогда не подойдет)

{'fName': None, 'lName': None', phones':[{'phoneNumber': None, 'phoneType': None}]}

Ваш варинат не найдет '‘phoneNumber’, нужно будет целый обьект передавать:

'phones':[{'phoneNumber': None, 'phoneType': None, 'comment': None}]

(rmerkushin) #13

Так вы делайте шаблон с конкретными значениями для замены (шаблон хранится в файле, вы его считываете как plain text), реплейс заменяет по всему тексту. Вы производите замену в текстовом шаблоне, а потом уже делаете сериализацию в json.

Возьмем для примера ваш json:

{'fName': None, 'lName': None', phones':[{'phoneNumber': None, 'phoneType': None}]}

К примеру вам нужно заменить значение в phoneNumber на 1234. Делаете следующий шаблон:

{'fName': None, 'lName': None', phones':[{'phoneNumber': $phoneNumber, 'phoneType': None}]}

И используйте следующий словарь для замены:

{'$phoneNumber': '1234'}

(Viktor Kliui) #14

спасибо, не работал никогда, а с большим объемом текста оно быстро работает?


(rmerkushin) #15

Если честно, не проверял на скорость, но думаю если у вас json’ы не по гигабайту, то должно хватать скорости :slight_smile:


(Bolatbek) #16

Работал с json размером с 120 мб. Ничего, все шустрит )


(Artur Korobeynyk) #17

120 мб - это 3 секунды на спокойной загрузке ОС на HDD. Скорость чтения HDD где-то 30-50 Мб\с, если вставить SSD, то получите время около секунды на обработку, если грузить с фэшки USB3 то тоже до секунды можно разогнать, если создать виртуальный диск в RAM, то 0.1 секунды примерно. Обработку структуры документа в расчет не берем, на современной системе она будет почти мгновенна.
Вобщем, делать то можно что угодно, но многометровые джисоны - это как-то дико. Я б нанверное xml для такой дикости припас.


(Bolatbek) #18

Там внутри просто файлы в base64 )


(Viktor Kliui) #19

Смотрите, мне в зависимости от пользователя нужно выбрать реквест и респонс, вот какую структуру я предусмотрел для этого:

 {'ROOT' : {'request':{"internalNumber": "1111","externalNumber": "0666816657"},
                   'response': {"internalNumber": "1111", "externalNumber": "0666816657"},
                   'status_code':200},
         'USER':{'request':{"internalNumber": "1111","externalNumber": "0666816657"},
                   'response': {'SCB_ROUTE_CREATE_EXCEPTION': 'User have no permissions'},
                   'status_code':403},
         'ADMINISTRATOR' : {'request':{"internalNumber": "1111","externalNumber": "0666816657"},
                   'response': {"internalNumber": "1111", "externalNumber": "0666816657"},
                   'status_code':200}}

Если я буду считывать как строку из файла, как выбрать реквест, респонс и статус код только для Юзера, например?


(Artur Korobeynyk) #20

Вы не считываете строку из файла. Вы считываете весь файл сразу и парсите его Json-м в dictionary структуру в память глобальной переменной на время всего теста, чтобы ускорить доступ к этой информации вдальнейшем. А потом просто обращаетесь типа CONFIG[‘USER’][‘request’][‘status_code’]

Вобщем погуглить: json в dict python