1.4. Разработка тестов

1.4. Разработка тестов

Тестирование приложений для системы TDG преследует две основные цели:

  • Проверка логики модулей (юнит-тесты);

  • Проверка продукта на соответствие требованиям (интеграционные тесты).

Юнит-тесты обычно синтетические и предназначены для исчерпывающего покрытия критических частей кода и последующей автоматической проверки на регрессии. Самая большая польза от юнит-тестов – выявление ситуаций, в которых модуль может получить разнообразный неверный «ввод» от пользователя. В таких случаях юнит-тесты помогают убедиться, что модуль реагирует на неверный ввод корректно. Если система приняла неверные по форме/содержанию данные или, наоборот, не приняла верные данные (посчитав их неверными), то юнит-тесты – идеальное средство проверки подобных сценариев.

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

Прежде чем разрабатывать собственные тесты, ознакомьтесь с существующими и запустите их.

1.4.1. Запуск тестов

Чтобы запустить существующие юнит-тесты, в корне проекта:

  1. Установите все зависимости при помощи скрипта:

    ./deps.sh
    

    Он установит в папку .rocks все модули, нужные для запуска.

  2. Запустите юнит-тесты:

    tarantool unit.lua
    

    Команда выполнит по очереди все юнит-тесты из папки test/unit.

Чтобы запустить существующие интеграционные тесты:

  1. Установите python3, pip и несколько python-библиотек. Весь список зависимостей приведен в файле requirements.txt в корне проекта. Чтобы установить их все глобально, используйте pip. Например:

    sudo pip3 install -r requirements.txt
    

    Примечание

    Как альтернативу можно настроить pipenv и сделать для прогона тестов отдельную среду.

  2. Запустите интеграционные тесты:

    pytest
    

1.4.2. Разработка юнит-тестов

Юнит-тесты сгруппированы по подсистемам в подпапках test/unit. Чтобы тест был включен в общий прогон, имя файла должно начинаться с test_ и иметь расширение .lua, например, test_ddl.lua. Команда tarantool unit.lua найдет в упомянутой папке файлы с такими именами и автоматически исполнит их.

Юнит-тесты используют модуль tap, который есть «на борту» Tarantool. Чтобы написать новый тест, используйте следующий код:

#!/usr/bin/env tarantool

local tap = require('tap')
local test = tap.test("validation")
test:plan(5) -- замените цифру на количество тестов в модуле

-- здесь будут тесты

 os.exit(test:check() and 0 or 1)

Чтобы написать сами тесты, достаточно вызывать у объекта test функции проверки:

test:isnil()
test:isstring()
test:isnumber()
test:istable()
test:isboolean()
test:isudata()
test:iscdata()

Если в эти функции будут переданы данные, не соответствующие ожидаемым, тест будет прерван с ошибкой и поясняющим сообщением.

Примечание

Чтобы быстро понять как писать юнит-тесты, лучше всего посмотреть в реализацию существующих.

Если в тестах требуется использовать box функции, используйте следующий код:

local tarantool = require('test.unit.tarantool').new()

tarantool:start()

-- здесь будут тесты

local success = test:check()

tarantool:stop()

os.exit(success and 0 or 1)

1.4.3. Разработка интеграционных тестов

Интеграционные тесты основаны на pytest, и «заточены» под тестирование логики приложения с возможным подключением логики кластера.

Для запуска каждого файла с тестами:

  1. Сначала поднимается «с нуля» TDG или кластер из узлов с разными ролями.

  2. Затем ко всем возможным узлам применяется соответствующая конфигурация.

  3. Наконец, по очереди запускаются тест-кейсы.

Интеграционные тесты находятся в подпапках test/integration. В отличие от юнит-тестов, они сгруппированы не по подсистеме, а по логической «близости» тестов друг к другу. Основной критерий группировки – тесты в одной подпапке имеют одну и ту же стартовую конфигурацию.

Чтобы создать новую группу тестов:

  1. Добавьте папку в test/integration и в ней сделайте подпапки config и data.

  2. В config поместите все конфигурационные файлы для TDG и connector. При исполнении этой группы тестов, такие файлы будут автоматически загружены в качестве начальной конфигурации.

  3. Дополнительно к папке config можно создать рядом папку data и положить туда тестовые данные, например, большие XML или JSON объекты. Это поможет не прописывать данные в коде теста в явном виде.

  4. Создайте файл с именем, начинающимся с test_ и расширением .py. Такие файлы автоматически включаются в прогон.

  5. В файле используйте следующий код:

    #!/usr/bin/env python3
    
    import pytest
    import os
    
    def test_something(server, datadir):
        # тут можно писать логику теста
        assert something == something_else
    
    # тут можно добавить еще тестов
    

Здесь функция-тест test_something имеет параметры server и datadir. Эти параметры называются «фикстуры» и они должны быть у каждой функции-теста. Фикстуры – это способ удобно использовать в тестах библиотечную функциональность и о них можно прочитать в документации по pytest.

При указании в параметрах datadir, можно получить полный путь к папке, в которой лежат тестовые данные текущей группы тестов.

Когда pytest видит в параметрах тестовой функции server:

  1. Он автоматически стартует TDG;

  2. Применяет к нему конфигурацию;

  3. И передает объект-обертку («враппер»), позволяющую удобно делать запросы (HTTP, SOAP, GraphQL и другие) к TDG.

1.4.3.1. API объекта-обертки

Объект-обертка сервера («враппер») поддерживает запросы через HTTP, SOAP и GraphQL:

  • server.post(path, data, json) – посылает post-запрос по пути path. Можно указать либо строку data для текстовых запросов, либо json для JSON запросов.

  • server.soap(data) – послает SOAP-запрос, где data – текст запроса.

  • server.graphql(query) – послает GraphQL-запрос, где query – текст запроса. В ответ функция либо бросает исключение при ошибке, либо возвращает объект с результатом. graphql для TDG разделён на схемы. Для доступа к данным schema = „default“ или не указывается. Для доступа к функциям администрирования schema = „admin“. Например запрос данных.

    server.graphql("""
      query {
        User(country:"USA") {
          fullname
        }
      }
    """)
    

    Пример вызова администрирования TDG, изменение модели:

     obj = server.post('/graphql', json={
        "query": "mutation set_model($model:String!) { model(model: $model) }",
        "variables": {
            "model": json.dumps(model)
        },
        "schema": "admin"
    })
    
  • server.cluster_graphql(query) – посылает GraphQL-запрос для управления кластером. Например,

    obj = server.cluster_graphql("""
       {
           servers {
               uri
               replicaset { roles }
           }
       }
    """)