Мой небольшой пример REST API Python Automation

rest
api
automation
testing
qa
python
Теги: #<Tag:0x00007fedbfcd9f38> #<Tag:0x00007fedbfcd9d08> #<Tag:0x00007fedbfcd9a10> #<Tag:0x00007fedbfcd9790> #<Tag:0x00007fedbfcd93a8> #<Tag:0x00007fedbfcd8d90>

#1

Rest API

Всем привет. Неделю назад я написал небольшой пример того как я попробовал автоматизировать на #mobile:appium + #programming:python . Сегодня я хотел показать и посоветоваться по поводу автоматизации #REST #API.

Почему посоветоваться? - Потому что иногда взгляд со стороны может помочь или улучшить.

Почему REST API ?

Сама по себе автоматизация API ,по моему опыту, очень простая. И если есть желание начать автоматизировать, то проще всего попробовать с API.

На python я использую библиотеку requests. Сама структура , как мне кажется - похожа на PageObject.
У нас на проекте микросервисная архитектура, каждый микросервис описывает два класса. Высокоуровневый и низкоуровневый.

Немного примеров.

Все куски кода покажу на примере небольшого тестового, которое я делал несколько месяцев назад. В описании используется REST API для github
Низкоуровневый подразумевает что на вход для каждого метода по работе с API приходит DTO, в котором есть набор необходимых параметров для запроса.

    def get_all_pull_requests(self, dto):
        """
        :param dto:
        :return:
        https://developer.github.com/v3/pulls/#list-pull-requests
        """
        return requests.get(url=self.session + '/repos/{owner}/{repo}/pulls'.format(
            owner=dto['owner'],
            repo=dto['repo']),
                            headers=self.header)

Высокоуровневый - в нем происходит генерация этого dto и передача его низкоуровневый + обрабляется сам ответ запроса. Происходит валидация status code и тд.

    def get_all_pull_requests(self):
        request = self.pull_request.get_all_pull_requests(self.all_pull_request_dto())
        if request.status_code != 200:
            raise ExpectedStatusCodeError
        return request.json()

Структура

Вот ссылка на проект.
Так организована структура проекта.


В API - описаные все сервисы (микросервисы).

В config - лежат все данные в .yaml файле и методы по работе с ними. Знаю что можно создать еще директорию helpers - куда все это поместить. Но, повторюсь - тут максимальноу упрощенный вариант. Добавлю что тесты прикручены к Travic-Ci, для этого был дополнительно создан .travic.yaml файл.

language: python
python:
  - "2.7"
# command to install dependencies
install: "pip install -r requirements.txt"
# command to run tests
script:
  - py.test -v py_tests/
  - behave bdd_tests/

И как бонус в самом hithub репозитории я могу видеть что тесты прошли, при работе с #mobile:appium или даже с #webdriver -так просто настроить тесты не получится.


Такую строчку добавить в README.md

### Status
[![Build Status](https://travis-ci.org/keyprqa/GitHubAPI.svg?branch=master)](https://travis-ci.org/keyprqa/GitHubAPI)

Выводы

Если хочеться начать автоматизировать - API это самый быстрый путь, в нем есть своя специфика. И это не мение интересно (для меня) чем UI автоматизация или мобайл. Как Вы описываете REST API автоматизацию?

  • Нравится писать тесты под API
  • Люблю старый, добрый Selenium
  • 50/50 от настроения

0 участников


(Viktor Kliui) #2

А Вы только 200 респонсы валидируете? Не проверяете правильно ли валидация отрабатывает? Правильный ли тип данных возвращается и т.д.


(ex3me0) #3


Любой кастомный экзепшн должен быть унаследован от BaseException (правила хорошего тона, как минимум)
Где юзаются вообще эти атрибуты? self.response, self.status_code
И что это там дальше за принт?
За экзепшны - двойка


Почему бы не импортировать все подряд, действительно. Это тоже двойка


Ну а че и тут формат не заюзать? Или уж сразу urljoin для спокойствия души. Ничего против конкатенации не имею, просто как-то инконсистент из серии “я художник, я так вижу” (чисто мое имхо)


Весь подход действительно так жестко завязан на конкретных версиях библиотек? Тот же реквестс уже давно за 18й мажор перескочил.
Часто юзается Faker как таковой? Стоит ли добавлять его как зависимость ради генерации одной-двух рандомных строк?


#4

@bitbok да и нет. На уровне самих запросов - только статус код. Тут описана сама АПИ.


#5

За експешины - мало с ними работал. Учту
За импорт - никогда все не импортирую - но в даной реализации - там в yaml не подразумевалось что будет много чего лежать ( но тоже согласен).
За версии библиотек - это тестовая версия - законсервированая - они не будет никогда использоваться как таковая. Faker - часто использую, не вижу ничего плохого. В даном вариант один раз, на проекте часто использую. А как Вы генерируете данные?


(ex3me0) #6

Зависит от потребностей генерируемой даты. Мои - не слишком накрученные, поэтому 99% кейсов:

''.join(random.choice(string.printable) for _ in range(data_length))

string.printable может быть заменен на леттерс+дигитс, ну или как угодно (зависит от потребностей опять же)


#7

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


#8

фот format. или я не так понял вопрос?


(ex3me0) #9

Немного не так
Я имел ввиду
urljoin(self.session, '/repos/{owner}/{repo}/pulls'.format(owner=dto['owner'], repo=dto['repo']), headers=self.header)
или
'{base_url}/repos/{owner}/{repo}/pulls'.format(base_url=self.session, owner=dto['owner'], repo=dto['repo']), headers=self.header)
Но это из серии “на вкус и цвет”, как я уже говорил, и при условиях что данные в self.session всегда поступают корректные (urljoin конечно может немного снивелировать лишний слеш в конце)


#10

у нас на проекте по-другому работа с сесиями устроена. тут максимально упростил :slight_smile:


(ex3me0) #11

Вот тут кстати не понял: а почему “сессия”? Там же тупо урл из конфига возвращается


#12
    self.session = requests.session()

вот как используем.
а тут даже это упростил) хотя большой разницы по написанию нет, а контекс конечно разный.


(ex3me0) #13

Идеология RESTful апи как раз в том, что сессия не поддерживается
Каждый новый запрос самодостаточен, и имеет необходимый набор данных для работы
У вас это как раз реализовано на уровне self.header = {‘Authorization’: ‘token {}’.format(token)}, и этот хедер прокидывается на каждый запрос
Поэтому опять таки идеологически - использование понятие “сессии” в контексте неуместно =\


#14

сессия держит в разрезе одного теста.


(ex3me0) #15

Не, так у нас получается разговор слепого с глухим. Давайте пруфы по сессии, ибо же я не увидел ничего связанного с сессией в коде.
Либо же вы слабо понимаете понятие сессии, как таковое, и почему RESTful API и сессия - вещи неуместные в одном предложении


#16

выше я уже написал что я на рабочем проекте использую сессис. а в этом приммере я упростил, заиспользовал просто реквесты. по написанию разницы почти нет , а по функционалу есть Вы просто невнимательно прочли.

@ex3me0


(Nikita Milka) #17

Спасибо за статью!


(Vatslau) #18

Что-то не понял… тут только коды проверяются?
тогда это в постмане можно за 5 минут набросать
Кодить стоит когда круд сценарии и валидации моделей нужны ИМХО


#19

Вы не внимательно смотрели. в API - описаные сами запросы и происходит валидация по статус коду на уровне запроса, а есть еще тесты в которых уже происходить все другие валидации. ± это тот же самый page object только для API