Рубрики
Без рубрики

Тестирование и издевание приложения Connexion / Flask с Pтой

В этой статье я покажу вам, как вы можете проверить веб-сервис Python, который был построен с использованием Connexion … Помечено тестированием, Python, колбой, Pтойцами.

В этой статье я покажу вам, как вы можете проверить веб-сервис Python, который был построен с использованием Connexion (Библиотека обертки вокруг колба). Мы перейдем за то, как вы можете издеваться над функциями и как вы можете проверить ваши конечные точки. Есть две связанные статьи, которые я написал в прошлом, перечисленном ниже. Во-первых, мы переходим, как создать веб-сервис с помощью Connexions, та же веб-сервис, которую мы будем в этой статье. Во второй статье я представляю, как вы можете использовать питиш-макет и pteest-flask Для проверки веб-службы Flask.

Примерное приложение мы будем писать тесты, – это очень простой Crud API, управляющий зоомагазином. Это позволяет нам добавлять домашние животные, удалять домашние животные, обновлять домашние животные и запрашивать домашние животные в магазине.

Структура

Вы можете найти исходный код здесь. Наша структура проекта выглядит так:

.
├── openapi
│   └── specification.yml
├── requirements.txt
├── test_api
│   ├── core
│   │   ├── __init__.py
│   │   ├── pets.json
│   │   └── pets.py
│   ├── __init__.py
│   ├── run.py
│   └── web
│       ├── controllers
│       │   ├── __init__.py
│       │   └── pets_controller.py
│       ├── encoder.py
│       ├── __init__.py
│       ├── models
│       │   ├── base_model_.py
│       │   ├── __init__.py
│       │   ├── pet.py
│       │   └── pets.py
│       └── util.py
└── tests
    ├── conftest.py
    ├── __init__.py
    └── test_pets_controller.py

API.

Вот наш модуль контроллера под названием Веб/Контроллер/PETS_CONTROLLER.PY Отказ Это где маршруты Connexion являются запросами:

import connexion
import six

from ..models.pet import Pet  # noqa: E501
from ..models.pets import Pets  # noqa: E501
from .. import util

from test_api.core import pets


def get_pet(pet_id):  # noqa: E501
    """Get a pet in the store

     # noqa: E501

    :param pet_id: The id of the pet to retrieve
    :type pet_id: str

    :rtype: Pet
    """
    try:
        pet = pets.get_pet(pet_id)
        response = Pet(id=pet.id, breed=pet.breed, name=pet.name, price=pet.price), 200
    except KeyError:
        response = {}, 404

    return response

Connexion использует спецификацию открытых API openapi/Спецификация.yml , чтобы работать, какая функция для маршрута запросов на путь /pet/{pet_id} Отказ Используется операция рядом с X-Swagger-Router-Controller Чтобы определить функцию, чтобы позвонить в PETS_CONTROLLER.PY модуль.

/pet/{pet_id}:
  get:
    tags:
      - "pet"
    summary: "Get a pet in the store"
    operationId: "get_pet"
    parameters:
      - name: "pet_id"
        in: "path"
        description: "The id of the pet to retrieve"
        required: true
        type: "string"
    responses:
      200:
        description: "Successfully retrived pet"
        schema:
          $ref: "#/definitions/Pet"
      404:
        description: "Pet doesn't exist"
    x-swagger-router-controller: "test_api.web.controllers.pets_controller"

Тесты

Сейчас на наши тесты!

Библиотеки

pteest-flask Позволяет указывать приложение приложению, а затем отправлять запросы API с помощью этого приложения. Использование похоже на Запросы Библиотека при отправке HTTP-запросов в наше приложение.

Pytest-Mock Простая обертка вокруг библиотеки моделей тестирования устройства, поэтому все, что вы можете сделать, используя Unittest.Mock Вы можете сделать с Pytest-Mock Отказ Основное отличие в использовании, вы можете получить доступ к нему, используя крепеж усадьба Также макет заканчивается в конце теста. Принимая во внимание, что с обычной библиотекой издевательства, если вы говорите, макет Открыть () Функция, она будет издеваться за оставшуюся продолжительность этого тестового модуля, то есть оно повлияет на другие тесты.

conftest.py

Conftest.py Файл автоматически работает PyTest и позволяет нашим тестовым модулям для доступа к приспособлениям, определенным в этом файле. Одна из лучших особенностей Pтойских приспособлений. Приспособление – это функции, которые имеют повторные использованные биты кода, мы можем запустить в наших модульных тестах, таких как статические данные, используемые тестами.

import os
import json

import pytest

from test_api.run import create_app


@pytest.fixture(scope="session")
def app():
    abs_file_path = os.path.abspath(os.path.dirname(__file__))
    openapi_path = os.path.join(abs_file_path, "../", "openapi")
    os.environ["SPEC_PATH"] = openapi_path

    app = create_app()
    return app


@pytest.fixture(scope="session", autouse=True)
def clean_up():
    yield
    default_pets = {
        "1": {"name": "ginger", "breed": "bengal", "price": 100},
        "2": {"name": "sam", "breed": "husky", "price": 10},
        "3": {"name": "guido", "breed": "python", "price": 518},
    }

    abs_file_path = os.path.abspath(os.path.dirname(__file__))
    json_path = os.path.join(abs_file_path, "../", "test_api", "core", "pets.json")
    with open(json_path, "w") as pet_store:
        json.dump(default_pets, pet_store, indent=4)

приложение()

В этом файле у нас есть две функции: приложение Позволяет пользователям пройти клиент Аргумент к другим тестам, а затем мы можем проверить ваше веб-приложение. Вы можете получить больше информации здесь О том, как приложения для колб могут быть проверены. По сути, нам не нужно начать/остановить сервер до/после наших тестов.

Давая это Scope = Session Смесь будет создан один раз, прежде чем все наши тесты выполняются. Наше Run.py Файл выглядит так:

import os

import connexion

from .web import encoder


def create_app():
    if "SPEC_PATH" in os.environ:
        openapi_path = os.environ["SPEC_PATH"]
    else:
        abs_file_path = os.path.abspath(os.path.dirname(__file__))
        openapi_path = os.path.join(abs_file_path, "../", "../", "openapi")
    app = connexion.FlaskApp(
        __name__,
        specification_dir=openapi_path,
        options={"swagger_ui": False, "serve_spec": False},
    )
    app.add_api("specification.yml", strict_validation=True)
    flask_app = app.app
    flask_app.json_encoder = encoder.JSONEncoder

    return flask_app

create_app Функция создает наше веб-приложение и возвращает объект Flask. Помните, что библиотека Connexion – это просто обертка вокруг колбы. Connexion просто уменьшает код котельной, который мы написали. Опять же, вы можете прочитать статью выше, чтобы получить более подробную информацию о том, как она работает.

clean_up ()

@pytest.fixture(scope="session", autouse=True)
def clean_up():
    yield
    default_pets = {
        "1": {"name": "ginger", "breed": "bengal", "price": 100},
        "2": {"name": "sam", "breed": "husky", "price": 10},
        "3": {"name": "guido", "breed": "python", "price": 518},
    }

    abs_file_path = os.path.abspath(os.path.dirname(__file__))
    json_path = os.path.join(abs_file_path, "../", "test_api", "core", "pets.json")
    with open(json_path, "w") as pet_store:
        json.dump(default_pets, pet_store, indent=4)

Второй прибор, который мы определяем, называется Clean_up из-за доходность Линия, эта функция будет работать после завершения всех наших испытаний. доходность Команда связана с генераторами, вы можете прочитать Больше здесь Отказ В нашем случае он используется в приборах Ptyest, чтобы мы могли запускать некоторые задания очистки после завершения нашего теста. В этом примере я просто заменяю содержимое файла JSON, который действует как хранилище данных (например, в базе данных), к его значениям по умолчанию, до того, как тест был запущен.

Поскольку Pteest-3.0, приспособления с использованием нормального декоратора прибора могут использовать оператор выхода для обеспечения значений прибора и выполнить код разрыва – Pteest Docs

test_pets_controller.py

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

def test_get_all_pets(client):
    url = "/api/v1/pet"
    expected_json = [
        {"id": "1", "name": "ginger", "breed": "bengal", "price": 100},
        {"id": "2", "name": "sam", "breed": "husky", "price": 10},
        {"id": "3", "name": "guido", "breed": "python", "price": 518},
    ]
    response = client.get(url)
    assert response.json == expected_json

Это очень простой тест, здесь мы используем приложение Приспособление, которое мы определены выше. Это клиент Крепеж можно использовать, потому что мы используем pteest-flask библиотека. Как вы можете видеть, это выглядит очень похоже на Запросы , где мы даем это путь /API/V1/Pet а затем скажи, что за просьба сделать Client.get Отказ В то время как синтаксис между Запросы Библиотека и клиент Приспособление практически идентична. Одна большая разница, которая всегда, похоже, отключается, в Запросы Чтобы получить данные JSON из ответ объект будет Ответ.json () I.e Это функция. Однако в клиент ( Pytest-Flask ) Приспособление Доберите данные JSON, которые мы делаем Ответ. Джусон который является просто атрибутом объекта, а не функции.

Сам тест очень прост, он приносит просьбу, чтобы получить все домашние животные в зоомагазине. Затем мы сравним, что мы ожидаем быть в зоомагазине Assert Response.json. .

Следующий тест, который у нас выглядит так:

@pytest.mark.parametrize(
    "pet_data, expected_status, expected_data",
    [
        ({"name": "Yolo", "breed": "shorthair", "price": 100}, 201, {"id": 4}),
        ({}, 400, {}),
        ({"a": "b"}, 400, {}),

    ]
)
def test_add_a_pet(client, pet_data, expected_status, expected_data):
    url = "/api/v1/pet"
    response = client.post(url, json=pet_data)
    assert response.status_code == expected_status
    if response.status_code == 200:
        assert response.json == expected_data

Этот тест пытается добавить новый питомец в магазин. Это похоже на другой тест, мы все еще используем клиент приспособление, чтобы сделать запрос. На этот раз мы также дам это немного JSON данные, следовательно, мы предоставляем JSON аргумент json = Pet_data Это автоматически устанавливает заголовки правильно, поэтому сервер знает, что он получает данные JSON.

Мы также используем украсьте @ pytest.mark.parametrize. . Это позволяет нам запустить наши тесты против списка данных. Поэтому нам не нужно писать тот же тест х количество раз. Мы просто передаем тестовые разные аргументы. Pтойцы запускают этот тест X номер раз один раз для каждого элемента в списке. Так, например, первый раз, когда тестовые прогоны:

pet_data = {"name": "Yolo", "breed": "shorthair", "price": 100}
expected_status = 200
expected_data = {"id": 4}

Второе, как это:

pet_data = {}
expected_status = 200
expected_data = {}

И так далее, и так далее. Это помогает сохранить наш тестовый файл меньше и сохраняет сухое (не повторять себя). Очень хорошая особенность Pтойца и один, который я использую сильно.

Последний тест, который у нас в этом файле выглядит:

def test_add_pet_fail_json(client, mocker):
    pet_data = {"name": "Yolo", "breed": "shorthair", "price": 100}
    url = "/api/v1/pet"
    mock = mocker.post("connexion.request")
    mock.is_json = False
    response = client.post(url, json=pet_data)
    assert response.status_code == 400

Наконец, мы видим Pytest-Mock используется через усадьба Приспособление мы автоматически получаем доступ к. усадьба просто простая обертка вокруг Unittest.Mock модуль. Основное отличие, будучи издевательства на существующих на протяжении всего теста. Издевание часто используется, когда тестирование единиц, и мы не можем полагаться на внешние зависимости, такие как соединения с базой данных или другой веб-сервис.

def add_pet(body):  # noqa: E501
    # ...
    if connexion.request.is_json:
        body = Pet.from_dict(connexion.request.get_json())  # noqa: E501
    # ...

В этом примере мы хотим издеваться со стороны Connexion, которая проверяет, является ли отправляемые данные действительными JSON. Мы хотим connexion.request.is_json вернуть Ложь мы можем сделать это так:

mock = mocker.patch("connexion.request")
mock.is_json = False

С IS_JSON это атрибут connexion.request Модуль и не функция, которую нам нужно установить ложь на другой строке. Если IS_JSON была функция, которую мы хотели вернуть Ложь Мы могли бы сделать mocker.patch ("connexion.request.is_json") вместо.

Вы можете запустить тесты локально, запустив pteest Команда или если вы хотите запустить код в этой статье, вы можете сделать следующее:

gcl https://gitlab.com/hmajid2301/articles.git
cd 27.\ Mocking\ in\ Flask\ with\ Pytest/source_code
virtualenv .venv
source .venv/bin/activate
pip install -r requirements.txt
pytest

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

Информация: pteest-flask Обеспечивает целую кучу других функций, которые могут быть полезны, вы можете найти полный список здесь

Приложение

Оригинал: “https://dev.to/hmajid2301/testing-mocking-a-connexion-flask-application-with-pytest-39fd”