- PVSM.RU - https://www.pvsm.ru -

Data Driven Testing

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

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

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

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

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

На сегодняшний момент, в случае падения теста происходит вывод того что упало, на каком эндпоинте, и 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. набросайте еще недостатков по коду и подходу к решению проблемы

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

Источник [9]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/260603

Ссылки в тексте:

[1] https://github.com/ydaniv/django-rest-assured: https://github.com/ydaniv/django-rest-assured

[2] bitbucket xml: https://bitbucket.org/sergeyglazyrindev/python-easytest/src/5428bcf5f209913b5d6ee799dee029de441c6b46/tests/django/project/easytest.xml?at=master&fileviewer=file-view-default

[3] ссылка на dtd файл: https://bitbucket.org/sergeyglazyrindev/python-easytest/src/5428bcf5f209913b5d6ee799dee029de441c6b46/easytest/dtd.xml?at=master&fileviewer=file-view-default

[4] bitbucket drf specific request, test visitor classes: https://bitbucket.org/sergeyglazyrindev/python-easytest/src/5428bcf5f209913b5d6ee799dee029de441c6b46/easytest/drf/request.py?at=master&fileviewer=file-view-default

[5] ссылка на код: https://bitbucket.org/sergeyglazyrindev/python-easytest/src/5428bcf5f209913b5d6ee799dee029de441c6b46/tests/django/project/project/easytest_extended/tests/test_resources.py?at=master&fileviewer=file-view-default#test_resources.py-12:25,40,41

[6] ссылка на код: https://bitbucket.org/sergeyglazyrindev/python-easytest/src/5428bcf5f209913b5d6ee799dee029de441c6b46/tests/django/project/project/easytest_extended/tests/test_resources.py?at=master&fileviewer=file-view-default#test_resources.py-43:51

[7] ссылка на код: https://bitbucket.org/sergeyglazyrindev/python-easytest/src/5428bcf5f209913b5d6ee799dee029de441c6b46/tests/django/project/project/easytest_extended/tests/test_resources.py?at=master&fileviewer=file-view-default#test_resources.py-63:84

[8] кликните, чтобы перейти на пример теста для django + drf: https://bitbucket.org/sergeyglazyrindev/python-easytest/src/5428bcf5f209913b5d6ee799dee029de441c6b46/tests/django/project/project/easytest_extended/tests/test_resources.py?at=master&fileviewer=file-view-default#test_resources.py-91:131

[9] Источник: http://habrahabr.ru/sandbox/110060/