Ответ
При проектировании фреймворка для тестирования CRUD API я бы выделил следующие логические классы или модули:
BaseAPIClient/BaseTestClass: Базовый класс для инкапсуляции общей логики: инициализация HTTP-клиента (с сессией, заголовками по умолчанию), обработка аутентификации (получение и подстановка токена), базовые методы для отправки запросов и логирования.TestDataFactory: Отдельный класс или модуль для генерации тестовых данных (валидных и невалидных). Это позволяет легко переиспользовать данные и поддерживать их консистентность.- Модули с тестами, сгруппированные по операциям:
test_create.py– тесты на создание сущности (POST). Проверяют коды ответа 201, корректность возвращенных данных, наличие обязательных полей.test_read.py– тесты на чтение (GET, GET список). Проверяют получение одной сущности, пагинацию, фильтрацию, сортировку.test_update.py– тесты на обновление (PUT, PATCH). Проверяют полное и частичное обновление, валидацию.test_delete.py– тесты на удаление (DELETE). Проверяют код 204 или 200, последующую недоступность ресурса.
Пример структуры теста:
# test_users.py
import pytest
class TestUsersCRUD:
def test_create_user(self, api_client, user_data):
"""POST /users"""
response = api_client.post("/users", json=user_data)
assert response.status_code == 201
created_user = response.json()
assert created_user["name"] == user_data["name"]
assert "id" in created_user
return created_user["id"] # Можно использовать в других тестах
@pytest.mark.parametrize("user_id, expected_code", [
("invalid_id", 404),
(999999, 404)
])
def test_get_user_negative(self, api_client, user_id, expected_code):
"""GET /users/{id} - негативные сценарии"""
response = api_client.get(f"/users/{user_id}")
assert response.status_code == expected_code
Дополнительно я бы выделил conftest.py для общих фикстур (например, @pytest.fixture для предсоздания и последующего удаления тестового пользователя) и отдельный модуль для тестов, связанных с бизнес-логикой, а не просто CRUD.