💻 Примеры: Проект API-тестов с Allure

← К оглавлению урока

⚡ Структура проекта API-тестов с Allure

project/
├── api/company_api.py        # @allure.step на всех методах API-клиента
├── db/company_table.py       # @allure.step на всех методах работы с БД
└── tests/test_x_clients_db.py  # тесты с @allure.epic/feature/story + шаги

Ключевой принцип: декорируй методы API-класса через @allure.step — тогда в тестах не нужно писать with allure.step для каждого вызова, Allure покажет их автоматически.

Структура проекта

project/
├── api/
│   └── company_api.py        # HTTP-клиент для работы с API компаний
├── db/
│   └── company_table.py      # Класс для работы с таблицей company в БД
├── tests/
│   └── test_x_clients_db.py  # Тесты с Allure-разметкой
├── allure-results/           # Папка с сырыми данными (создаётся автоматически)
└── allure-report/            # Папка с HTML-отчётом (создаётся при generate)

Пример 1 — Класс CompanyApi с @allure.step

# api/company_api.py
import allure
import requests

class CompanyApi:

    def __init__(self, url):
        self.url = url

    @allure.step("api. Получить список компаний")
    def get_company_list(self, params_to_add=None):
        my_url = self.url + "/company"
        response = requests.get(my_url, params=params_to_add)
        return response.json()

    @allure.step("api. Получить токен авторизации пользователя '{user}'")
    def get_token(self, user="raphael", password="cool-but-crude"):
        creds = {"user": user, "password": password}
        response = requests.post(self.url + "/auth/login", json=creds)
        return response.json()["userToken"]

    @allure.step("api. Получить компанию по id '{id}'")
    def get_company(self, id):
        response = requests.get(self.url + "/company/" + str(id))
        return response.json()

    @allure.step("api. Создать компанию '{name}' ({description})")
    def create_company(self, name, description=""):
        token = self.get_token()
        headers = {"x-client-token": token}
        body = {"name": name, "description": description}
        response = requests.post(self.url + "/company", headers=headers, json=body)
        return response.json()

    @allure.step("api. Редактировать компанию '{new_id}': новое имя '{new_name}'")
    def edit(self, new_id, new_name, new_descr):
        token = self.get_token()
        headers = {"x-client-token": token}
        body = {"name": new_name, "description": new_descr}
        response = requests.patch(
            self.url + "/company/" + str(new_id),
            headers=headers,
            json=body
        )
        return response.json()

    @allure.step("api. Удалить компанию '{id}'")
    def delete(self, id):
        token = self.get_token()
        headers = {"x-client-token": token}
        response = requests.delete(
            self.url + "/company/" + str(id),
            headers=headers
        )
        return response.json()

    @allure.step("api. (Де)активировать компанию '{id}', isActive='{isActive}'")
    def set_active_status(self, id, isActive):
        token = self.get_token()
        headers = {"x-client-token": token}
        body = {"isActive": isActive}
        response = requests.patch(
            self.url + "/company/" + str(id),
            headers=headers,
            json=body
        )
        return response.json()

Пример 2 — Класс CompanyTable с @allure.step и вложениями

# db/company_table.py
import allure
from sqlalchemy import create_engine
from sqlalchemy.sql import text

class CompanyTable:
    __scripts = {
        "select":           "select * from company",
        "select_active":    'select * from company where "is_active" = true',
        "delete_by_id":     text("delete from company where id = :id_to_delete"),
        "insert_new":       text("insert into company (name) values (:new_name)"),
        "select_max_id":    "select max(id) from company",
    }

    def __init__(self, connection_string):
        self.__db = create_engine(connection_string).connect()

    @allure.step("БД. Запросить список организаций")
    def get_companies(self):
        query = self.__db.execute(self.__scripts["select"])
        allure.attach(
            str(query.context.cursor.query),
            "SQL-запрос на получение списка компаний",
            allure.attachment_type.TEXT
        )
        return query.fetchall()

    @allure.step("БД. Запросить список активных организаций")
    def get_active_companies(self):
        query = self.__db.execute(self.__scripts["select_active"])
        allure.attach(
            str(query.context.cursor.query),
            "SQL-запрос на получение активных компаний",
            allure.attachment_type.TEXT
        )
        return query.fetchall()

    @allure.step("БД. Запросить организацию по {id}")
    def get_company_by_id(self, id):
        query = self.__db.execute(
            text("select * from company where id = :cid"),
            {"cid": id}
        )
        allure.attach(
            str(query.context.cursor.query),
            "SQL-запрос на получение компании по id",
            allure.attachment_type.TEXT
        )
        return query.fetchone()

    @allure.step("БД. Удалить организацию по {id}")
    def delete(self, id):
        params = {"id_to_delete": id}
        query = self.__db.execute(self.__scripts["delete_by_id"], parameters=params)
        allure.attach(
            str(query.context.cursor.query),
            "SQL-запрос на удаление компании",
            allure.attachment_type.TEXT
        )

    @allure.step("БД. Создать организацию с названием {name}")
    def create(self, name):
        params = {"new_name": name}
        query = self.__db.execute(self.__scripts["insert_new"], parameters=params)
        allure.attach(
            str(query.context.cursor.query),
            "SQL-запрос на создание компании",
            allure.attachment_type.TEXT
        )

    @allure.step("БД. Получить максимальный id организации")
    def get_max_id(self):
        query = self.__db.execute(self.__scripts["select_max_id"])
        allure.attach(
            str(query.context.cursor.query),
            "SQL-запрос на получение максимального id",
            allure.attachment_type.TEXT
        )
        return query.fetchone()[0]

Пример 3 — Тесты с разметкой Allure и шагами

# tests/test_x_clients_db.py
import allure
from api.company_api import CompanyApi
from db.company_table import CompanyTable

@allure.epic("компании")
@allure.severity("blocker")
class CompanyTest:

    api = CompanyApi("https://x-clients-be.onrender.com")
    db  = CompanyTable(
        "postgresql://x_clients_user:password@host/x_clients_db"
    )

    # --- Тест 1: Получение полного списка компаний ---

    @allure.id("ITCH-1")
    @allure.story("Получение списка компаний")
    @allure.feature("READ")
    @allure.title("Получение полного списка организаций")
    def test_get_companies(self):
        api_result = self.api.get_company_list()   # → шаг "api. Получить список компаний"
        db_result  = self.db.get_companies()       # → шаг "БД. Запросить список организаций"
        assert len(api_result) == len(db_result)

    # --- Тест 2: Только активные компании ---

    @allure.id("ITCH-2")
    @allure.story("Получение списка компаний")
    @allure.feature("READ")
    @allure.title("Получение списка активных организаций")
    @allure.description("Запрос организаций с параметром active = true")
    def test_get_active_companies(self):
        api_result = self.api.get_company_list(params_to_add={"active": True})
        db_result  = self.db.get_active_companies()
        assert len(api_result) == len(db_result)

    # --- Тест 3: Создание компании (вложенные шаги) ---

    @allure.id("ITCH-3")
    @allure.story("Создание компаний")
    @allure.feature("CREATE")
    @allure.title("Создание организации")
    def test_add_new(self):
        with allure.step("Получить количество организаций ДО"):
            body       = self.api.get_company_list()
            len_before = len(body)

        with allure.step("Создать организацию"):
            with allure.step("Сгенерировать название"):
                name  = "Autotest"
                descr = "Autotest description"
            with allure.step("Вызвать API-метод для создания"):
                result = self.api.create_company(name, descr)
                new_id = result["id"]

        with allure.step("Проверить поля новой организации"):
            body = self.api.get_company_list()
            found = next((c for c in body if c["id"] == new_id), None)
            assert found is not None
            assert found["name"]        == name
            assert found["description"] == descr

        with allure.step("Получить количество организаций ПОСЛЕ"):
            body      = self.api.get_company_list()
            len_after = len(body)

        with allure.step("Проверить, что список ДО меньше списка ПОСЛЕ на 1"):
            assert len_after - len_before == 1

        with allure.step("Удалить из БД новую организацию"):
            self.db.delete(new_id)

    # --- Тест 4: Получение компании по id ---

    @allure.id("ITCH-4")
    @allure.story("Получение компании по id")
    @allure.feature("READ")
    @allure.title("Получение организации по id")
    def test_get_one_company(self):
        body   = self.api.get_company_list()
        cid    = body[0]["id"]
        result = self.api.get_company(cid)
        assert result["id"] == cid

Пример 4 — Базовая аннотация + шаг (минимальный пример)

# tests/test_login.py
import allure

@allure.feature("Авторизация")
@allure.story("Успешный вход пользователя")
def test_user_login():
    with allure.step("Открытие страницы авторизации"):
        pass  # здесь: driver.get(url) или requests.get(url)

    with allure.step("Ввод логина и пароля"):
        pass  # здесь: отправка credentials

    with allure.step("Проверка успешного входа в систему"):
        assert True  # здесь: assert response.status_code == 200

Пример 5 — Вложение лога выполнения

# tests/test_attachment_demo.py
import allure

def test_with_text_attachment():
    log = "Запрос: GET /company\nОтвет: 200 OK\nДанных: 5 записей"
    allure.attach(log, name="Лог выполнения теста",
                  attachment_type=allure.attachment_type.TEXT)
    assert True