Автор : другие произведения.

Фреймворк для автоматизации на основе Pytest

Самиздат: [Регистрация] [Найти] [Рейтинги] [Обсуждения] [Новинки] [Обзоры] [Помощь|Техвопросы]
Ссылки:
Школа кожевенного мастерства: сумки, ремни своими руками
 Ваша оценка:

Обзор 

Тестирование API - это тип интеграционных тестов, представляющих полноценное тестирование для SaaS продуктов. В контексте интеграционных тестов сложной задачей является зависимость компонентов. Хороший интеграционный тест хорошо проверяет взаимодействие между несколькими компонентами или API. Кроме того, создание герментичных окружений полезно для повышения эффективности тестирования и уверенности в проведении интеграционных тестов. 

Данный документ поможет построить архитектуру pytest. Основные модели будут разделены между тремя контроллерами Module, Logic, Validation. В принципе, все контроллеры должны работать над главной целью.

Test Case Module

Человекочитаемый интерфейс для разработки тестовых шагов в этом модуле.
В этом объекте (классе) мы фокусируемся на пользовательском сценарии, каждый тест-кейс содержит четкие шаги с действиями или проверками. В качестве тестового фреймворка можем использовать фикстуру pytest для инициализации и финализации модуля. Кроме того, с помощью этого механизма можно задать жизненный цикл и scope с большой гибкостью. 

Следующий пример показывает как выглядит Test Case Module.

from controller.example_feature.users import *
import pytest


@pytest.fixture(scope="function", autouse=True)
def prepare_params(setup):
    routed_path = '/users/weigo'
    api_url = f'{setup.url_prefix}{routed_path}'
    setup.logic_controller = Users(api_url, setup.dataset)
    yield setup


class TestGetUsers:
    def test_rat_get_users(self, setup):
        # Provide Steps
        result = setup.logic_controller.get_users()
        # Provide Verfication
        UsersAssertion.verify_general_response_code_200(result)

Контроллер Logic

Этот шаблон проектирования аналогичен page object паттерну. Если веб-страница является единицей класса, API - похожая концепция что и page object. Каждый API должен содержать определенные методы и функции. Кроме того, мы должны учесть зависимость между двумя API. Лучшая практика - попытаться изолировать каждый API. Преимущество заключается в следующем: 

Стандартный restful API может иметь несколько http-методов вроде GET, POST, PUT, PATCH, DELETE. Для построения логического контроллера используется название роута URL вроде https://api.github.com/users/weigo
Согласно пути выше мы будем использовать users в качестве имени логического контроллера, затем создадим класс Users(Base) и класс UsersAssertion(BaseAssertion). Все функции в классе users связаны с конкретной функцией API, структурой данных и наборе действий для работы с этой функцией.

from controller.api_util.base_logic_controller import Base, BaseAssertion


class Users(Base):
    def __init__(self, url, data_set):
        Base.__init__(self)
        self.data_set = data_set
        self.url = url

    def get_users(self):
        res = self.send_request(
            Base.RequestMethod.GET,
            custom_url=self.url
        )
        return res


class UsersAssertion(BaseAssertion):
    @classmethod
    def verify_specific_results(cls, res: Base.ResponseObject):
        # Here to verify specific results from Response Object
       pass

класс Base создается для общих или базовых функций общего доступа API, например: реализация отправки, обработка вложений или обработчик ответа. 

from .common_imports import *
import uuid
import requests
from requests.models import Response


class Base(object):
    class ResponseObject(object):
        def __init__(self, response: Response):
            self.status_code = response.status_code
            self.content = response.content
            self.text = response.text
            try:
                self.json = response.json()
            except Exception as e:
                self.json = None
                logger.warning(e)
            self.header = response.headers
            self.url = response.url

    class RequestMethod(str, Enum):
        GET = "GET"
        POST = "POST"
        PUT = "PUT"
        DELETE = "DELETE"
        PATCH = "PATCH"

    def __init__(self) -> None:
        pass

    @staticmethod
    def gen_unique_str():
        id = uuid.uuid4()
        return id

    def send_request(self,
                     method: RequestMethod = RequestMethod.GET,
                     payload=None,
                     chunk_size: int = 0,
                     cookies=None,
                     custom_url: str = None,
                     headers=None,
                     files: list = None
                     ) -> ResponseObject:

        _payload = None

        if files:
            _payload = {}

        if custom_url is None:
            logging.warning("should provide url when sending request")

        if payload is None:
            logging.warning("should provide payload when sending request")
        else:
            _payload = payload

        if headers is None:
            _headers = {"Content-Type": "application/json"}
        else:
            _headers = headers

        # new request session
        res = None
        session_res = requests.session()

        if method is self.RequestMethod.GET:
            res = session_res.get(custom_url, headers=_headers, cookies=cookies, stream=True)
        elif method is self.RequestMethod.POST:
            res = session_res.post(custom_url, headers=_headers, cookies=cookies, stream=True, json=_payload, files=files)
        elif method is self.RequestMethod.PUT:
            res = session_res.put(custom_url, headers=_headers, cookies=cookies, stream=True, json=_payload, files=files)
        elif method is self.RequestMethod.DELETE:
            res = session_res.delete(custom_url, headers=_headers, cookies=cookies, stream=True)
        elif method is self.RequestMethod.PATCH:
            res = session_res.patch(custom_url, headers=_headers, cookies=cookies, stream=True, json=_payload, files=files)
        else:
            res = session_res.put(custom_url, headers=_headers, cookies=cookies, stream=True, data=_payload, files=files)

        res_obj = self.ResponseObject(res)

        # Formalize output logs for all API tests
        logger.info("\n=============URL=================\n")
        logger.info(res_obj.url)
        logger.info("\n============Payload==============\n")
        logger.info(payload)
        logger.info("\n============Response=============\n")
        if len(res.content) < 10000:
            logger.info(res_obj.text)
        else:
            logger.info("Too large response body, skipped to print.")
        logger.info("\n=================================\n")

        return res_obj

Контроллер валидации

В идеале каждый API интерфейс должен иметь собственную логику проверки, поэтому мы ожидаем, что каждый логический контроллер будет иметь класс проверки. Кроме того, мы должны четко разделить уровень уровень функции и тестовой проверки, чтобы каждый контроллер работал над своей целью. С другой стороны, иногда API имеет сложные комплексные шаги валидации и если мы реализуем их в одном и том же месте, поддерживать наши тест-кейсы может быть сложно. С помощью этого шаблона проектирования мы можем дать возможность писать разработчику правильный код в нужном месте. 

Структура класса валидации такая же как у логического контроллера. Все классы проверки наследуются от BaseAssetion, куда можно встраивать общие функции проверки или базовые функции. Например: проверка 200, 500 кодов, плохой запрос 400 или контент не в json формате.

Какова лучшая практика для реализации проверок?

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

Убедитесь, что ваш API может быть протестирован без каких-либо зависимостей, чтобы провести автономное тестирование API есть два подхода:

Тестовые данные

В этой статье мы используем yml для хранения предварительно подготовленных тестовых данных в {root}/testdata/*.yml 
когда вы запускаете pytest для определенного набора тестов, укажите путь в качестве параметра в командной строке

pytest ./testsuite/example_feature/test_get_users.py --env=STAGING --dataset={feature_folder}/test_data

Ссылки

Вы можете скачать Демо-проект для реализации вашей собственной интеграционной платформы API.

Особенности:


 Ваша оценка:

Связаться с программистом сайта.

Новые книги авторов СИ, вышедшие из печати:
О.Болдырева "Крадуш. Чужие души" М.Николаев "Вторжение на Землю"

Как попасть в этoт список

Кожевенное мастерство | Сайт "Художники" | Доска об'явлений "Книги"