Описание тестовых моделей

Структура описания

В основе тестовых моделей лежит следующая структура каталогов

test_framework
|--test_models
|  |--api
|  |  |--base.py 
|  |  |--api_model_1.py
|  |  |--api_model_2.py
|  |  |--...
|  |-- DB
|  |-- UI

Назначение файла base.py

В данном файле описаны правила взаимодействия с тестируемым сервисом.
Пример,

class BaseService:
    BASE_URL = "https://api.skillcrafts.ru"

    def __init__(self):
        self.last_response: Optional[requests.Response] = None

    def _get_headers(self) -> dict:
        """Базовые заголовки, наследники могут добавлять авторизацию."""
        return {
            "Content-Type": "application/json",
            "Accept": "application/json"
        }

    def _request(self, method: str, path: str, **kwargs) -> requests.Response:
        url = f"{self.BASE_URL}/{path.lstrip('/')}"
        headers = self._get_headers()
        if 'headers' in kwargs:
            headers.update(kwargs.pop('headers'))
        self.last_response = requests.request(method, url, headers=headers, **kwargs)
        return self.last_response

    def get(self, path: str, **kwargs) -> requests.Response:
        return self._request("GET", path, **kwargs)

    def post(self, path: str, data: Optional[dict] = None, **kwargs) -> requests.Response:
        return self._request("POST", path, json=data, **kwargs)

    def put(self, path: str, data: Optional[dict] = None, **kwargs) -> requests.Response:
        return self._request("PUT", path, json=data, **kwargs)

    def delete(self, path: str, **kwargs) -> requests.Response:
        return self._request("DELETE", path, **kwargs)

Назначение файла api_model_1.py

Данный файл предназначен для описания правил работы с ендпоинтами REST API в виде классов по принципу: одна схема (schema) - один класс.
В каждом классе определяются методы запросов. Один метод - один ендпоинт.

Например, у меня есть ендпоинт, запрашивающий JWT-токены.
Тогда я пишу класс, который будет хранить токены и мета-данные, соответствующие модели данных для этого эндпоинта.

class Auth(BaseService, ApiBaseAssertsMixin):
    """
    Модель данных описывается аналогично фреймворкам django и FastAPI
    """
    access_token: str | None = None   # токен
    refresh_token: str | None = None  # токен
    user_id: str | None = None        # мета-информация

А также метод, который будет делать запрос этих данных.

    """Начало класса в листинге выше"""

    def token(self, email: str, password: str) -> dict:
        """Получает access + refresh токены + мета данные"""
        self.post("/auth/token/", data={"email": email, "password": password})
        tokens = self.last_response.json()
        Auth.access_token = tokens.get("access")
        Auth.refresh_token = tokens.get("refresh")
        Auth.user_id = tokens.get("user_id")
        return tokens

Далее нужно подмешать в класс миксины с методами-ассертами, которые нужны будут для тест-дизайна. Подробнее о тест-дизайне в резделе wiki/test_design/.

В приведённым выше классе подмешивается миксин с общими для любого ендпоинта ассертами, такими как проверка статус-кода, проверка наличия токена для заголовка Authorization и т.д.

Миксины с ассертами разрабатываются в отдельном блоке фреймворка "Фабрика тестов": test_factories.