Data Driven Testing

в 14:59, , рубрики: django, python, Тестирование веб-сервисов, метки: , , , ,

Добрый день уважаемые жители !

Работая на текущем проекте, я столкнулся с проблемой однотипных данных и необходимостью протестировать разные http вызовы над одними и теми же данными.
Проект разрабатывается на django/django-rest-framework/python3.5.
Поначалу я начал использовать этот движок для упрощения тестирования django-rest-framework (django-rest-assured — https://github.com/ydaniv/django-rest-assured).

Но, имея необходимость протестировать по сути одни и те же данные на разных урлах, я осознал, что использование этого движка не помогло так уж сильно облегчить задачу.
Конечно, в какой то мере проект стал более податливым для тестирования. Но, возникало много вопросов с так называемыми django-rest-framework detail_route и list_route. Тем, кто не в курсе напомню, эти декораторы позволяют определить специфические действия выполняющиеся над каким то конкретным типом данных (дальше, ресурсом).
И потом я понял, что тесты в проекте нужно группировать относительно ресурсов (типов) данных, над которыми они тестируются.

А есть ли реальная необходимость в этом ?

К примеру, возьмем объект машина, стоит цель продать машину, какие действия могут быть выполнены над ней и какие типы запросов могут быть в отношении этого объекта (машина):

  1. в исправном ли она состоянии ?
  2. если в неисправном, то как дорого и долго будет ее восстановить ?
  3. если в исправном, то проверка ее статуса с определенной периодичностью
  4. сделать ставку в отношении машины, товара (если мы говорим об аукционах)

Это все будет одна машина, одна запись из базы данных.
Но разные http запросы. Я согласен, что может быть не совсем удачный пример с машиной, ибо http запросами не оценить стоимость восстановления машины… Да и понятно же что много из этих данных могут быть возвращены при GET запросе касательно этой машины (и дополнительные http запросы могут не потребоваться), но предположим это не так. И тут возникает проблема: у нас одна машина, но куча тестов для каждой из операций над этой машиной. Почему бы не сгруппировать все эти тесты в один набор тестов но над одним ресурсом.
Я приведу пример xml, который можно было бы написать для набора таких тестов:

       <resource id="normal_car">
          <rest url="/api/core/cars/{id}/is_fine/" method="get" />
          <rest url="/api/core/cars/{id}/repair_cost_and_time/" method="get" />
          <rest url="/api/core/cars/{id}/is_available/" method="get" />
          <rest url="/api/core/cars/{id}/bet/" method="post" />
      </resource>

Сразу замечу, почему я здесь привожу ссылку с {id}, я использую в easytest движке open api схему для генерации фикстур для запроса и тд и тп.
И в openapi схеме ссылка для detail запросов связанных с конкретных ресурсом генерируются как:

        "/api/core/cars/{id}/bet/": {
            "post”: {
                "consumes": [
                    "application/json"
                ],
                "description": "Description",
                "operationId": "operationId",
                "parameters": [
                    {
                        "description": "",
                        "in": "path",
                        "name": "id",
                        "required": true,
                        "type": "string"
                    },
                    {
                        "in": "body",
                        "name": "data",
                        "schema": {
                            "properties": {
                                "price": {
                                    "description": "",
                                    "type": "integer"
                                },
                            },
                            "required": [
                                "price"
                            ],
                            "type": "object"
                        }
                    }
                ],
            }
        }

И что дальше ?

Затем, имея open api схему, мы можем:

  1. сгенерировать фикстуру для данного http вызова, например, в случае с bet/ это может быть: {‘price’: 1000}
  2. сделать нужный http вызов и проверить результат, который вернул сервер.

Именно для этого я сделал easytest движок.
Во первых, никакого кода для тестов не генерируется. Зачем нам код для тестов если схема для тестов описана уже в open api схеме? Единственное что нам нужно – это возможность определять кастомные setup, teardown, matcherы для http вызовов, и это все что нам нужно иметь возможность делать для осуществления тестирования. Кстати, нужно еще не забывать что разные http вызовы могут требовать разного режима базы данных, некоторые — READ_UNCOMMITED, некоторые — READ COMMITED. Это тоже учтено в движке.
Теперь по порядку о том как все работает:

  1. мы определяем схему тестирования для нашего приложения
    Вот пример из тестов проекта:
    bitbucket xml
  2. Вот dtd для валидации схемы тестирования:
    ссылка на dtd файл
    Как вы видите, запросы могут быть anonymous, transactional.
    На данный момент поддерживается только один framework в движке для тестирования: drf — Это django rest framework. Но, добавление нового frameworkа очень просто, нужно переопределить два класса:
    bitbucket drf specific request, test visitor classes
  3. Очевидно, что разные типы данных могут иметь разные требования по валидации, например пароли, как правило эти правила валидации уникальны для проекта, для этого мы имеем возможность регистрировать кастомные data генераторы:
    ссылка на код
    Причем, эти кастомные генераторы имеют доступ к текущему requestу, так что они могут вернуть корректные данные для разных эндпоинтов. Таким образом, мы можем реализовать практически любые требования по валидным, инвалидным данным в движке для тестирования.
  4. Теперь об одной из ключевых вещей для проекта:
    это реестр ресурсов:
    есть один ресурс, который необходим всегда, в случае если у вас есть хоть один не анонимный тест (запрос):
    current_user → пример с того как это реализовано в тесте для django
    ссылка на код
  5. Кастомные teardown, setup, custom matcher возможно определить следующим образом:
    ссылка на код

Однако, вы должны определить один тест, в котором вы сделаете инициализацию easytest и напишете что то подобное:
кликните, чтобы перейти на пример теста для django + drf

Как понять что ошибка произошла ?

На сегодняшний момент, в случае падения теста происходит вывод того что упало, на каком эндпоинте, и traceback exceptionа в консоль (терминал) в красном цвете. И если хоть один тест упал, то падает весь тест test_resources с следующим сообщением:
Automatic test of endpoints has been failed
Пока еще не опубликовал пакет на pypi. Еще думаю над названием, но уже использую в своем проекте, в котором порядка 100 эндпоинтов. Сильно облегчает жизнь

И да, решение не идеальное

Из недостатков:

  1. добавить комментарии, type hints для упрощенной навигации по коду, помогает сильно в редакторах где есть хороший python type hinting
  2. реализовать настоящий инжектор специфичных для движка классов. Пока инжектинг работает просто как локатор. Но есть идея заюзать pypi пакет injector. Правда я хочу чтобы пакетом пользовались и под python2 (хотя еще не проверял под python2). С маленькими фиксами будет работать
  3. набросайте еще недостатков по коду и подходу к решению проблемы

Автор: незнакомец

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js