Примеры тестов API, сгенерированных ИИ
Updated Jul 2026
Полный пример сгенерированных тестов
Этот файл демонстрирует, что создаёт ИИ на основе OpenAPI-схемы при правильно составленном промпте, с комментариями о том, что делает каждый тест эффективным.
Сгенерированный файл тестов
import pytest
import httpx
from uuid import uuid4
BASE_URL = "https://api.staging.example.com"
# ============================================================
# Fixtures
# ============================================================
@pytest.fixture
def auth_token():
"""Get a valid user-level auth token."""
return get_test_token(role="user")
@pytest.fixture
def admin_token():
"""Get a valid admin-level auth token."""
return get_test_token(role="admin")
@pytest.fixture
def seed_product(admin_token):
"""Create a product for testing, clean up afterward."""
response = httpx.post(
f"{BASE_URL}/api/v2/products",
headers={"Authorization": f"Bearer {admin_token}"},
json={"name": "Test Widget", "price": 29.99, "category": "electronics"}
)
product = response.json()
yield product
# Cleanup
httpx.delete(
f"{BASE_URL}/api/v2/products/{product['id']}",
headers={"Authorization": f"Bearer {admin_token}"}
)
# ============================================================
# GET /api/v2/products/{id}
# ============================================================
class TestGetProduct:
"""GET /api/v2/products/{id}"""
def test_get_existing_product(self, auth_token, seed_product):
"""Happy path: retrieve a known product."""
response = httpx.get(
f"{BASE_URL}/api/v2/products/{seed_product['id']}",
headers={"Authorization": f"Bearer {auth_token}"}
)
assert response.status_code == 200
body = response.json()
assert body["id"] == seed_product["id"]
assert body["name"] == seed_product["name"]
assert body["category"] in ["electronics", "clothing", "food", "other"]
assert isinstance(body["price"], (int, float))
assert body["price"] >= 0
def test_get_nonexistent_product(self, auth_token):
"""404 when product ID does not exist."""
fake_id = str(uuid4())
response = httpx.get(
f"{BASE_URL}/api/v2/products/{fake_id}",
headers={"Authorization": f"Bearer {auth_token}"}
)
assert response.status_code == 404
def test_get_product_invalid_uuid(self, auth_token):
"""400 or 422 when ID is not a valid UUID."""
response = httpx.get(
f"{BASE_URL}/api/v2/products/not-a-uuid",
headers={"Authorization": f"Bearer {auth_token}"}
)
assert response.status_code in [400, 422]
def test_get_product_no_auth(self):
"""401 when no authorization header is provided."""
response = httpx.get(f"{BASE_URL}/api/v2/products/{uuid4()}")
assert response.status_code == 401
# ============================================================
# PUT /api/v2/products/{id}
# ============================================================
class TestUpdateProduct:
"""PUT /api/v2/products/{id}"""
def test_update_product_as_admin(self, admin_token, seed_product):
"""Happy path: admin updates a product."""
response = httpx.put(
f"{BASE_URL}/api/v2/products/{seed_product['id']}",
headers={"Authorization": f"Bearer {admin_token}"},
json={"name": "Updated Widget", "price": 29.99, "category": "electronics"}
)
assert response.status_code == 200
assert response.json()["name"] == "Updated Widget"
def test_update_product_as_non_admin(self, auth_token, seed_product):
"""403 when non-admin tries to update."""
response = httpx.put(
f"{BASE_URL}/api/v2/products/{seed_product['id']}",
headers={"Authorization": f"Bearer {auth_token}"},
json={"name": "Hacked", "price": 0}
)
assert response.status_code == 403
def test_update_missing_required_name(self, admin_token, seed_product):
"""400 when required field 'name' is missing."""
response = httpx.put(
f"{BASE_URL}/api/v2/products/{seed_product['id']}",
headers={"Authorization": f"Bearer {admin_token}"},
json={"price": 10.00} # name is missing
)
assert response.status_code == 400
def test_update_missing_required_price(self, admin_token, seed_product):
"""400 when required field 'price' is missing."""
response = httpx.put(
f"{BASE_URL}/api/v2/products/{seed_product['id']}",
headers={"Authorization": f"Bearer {admin_token}"},
json={"name": "Widget"} # price is missing
)
assert response.status_code == 400
def test_update_negative_price(self, admin_token, seed_product):
"""400 when price is below minimum (0)."""
response = httpx.put(
f"{BASE_URL}/api/v2/products/{seed_product['id']}",
headers={"Authorization": f"Bearer {admin_token}"},
json={"name": "Widget", "price": -5.00}
)
assert response.status_code == 400
@pytest.mark.parametrize("name", [
"", # Below minLength (0 chars)
"A" * 201, # Above maxLength (201 chars)
])
def test_update_name_boundary(self, admin_token, seed_product, name):
"""400 when name violates length constraints."""
response = httpx.put(
f"{BASE_URL}/api/v2/products/{seed_product['id']}",
headers={"Authorization": f"Bearer {admin_token}"},
json={"name": name, "price": 10.00}
)
assert response.status_code == 400
@pytest.mark.parametrize("name", [
"A", # At minLength (1 char)
"A" * 200, # At maxLength (200 chars)
"Normal Product", # Nominal value
])
def test_update_name_valid_boundaries(self, admin_token, seed_product, name):
"""200 when name is within valid length range."""
response = httpx.put(
f"{BASE_URL}/api/v2/products/{seed_product['id']}",
headers={"Authorization": f"Bearer {admin_token}"},
json={"name": name, "price": 10.00}
)
assert response.status_code == 200
def test_update_invalid_category_enum(self, admin_token, seed_product):
"""400 when category is not in the allowed enum."""
response = httpx.put(
f"{BASE_URL}/api/v2/products/{seed_product['id']}",
headers={"Authorization": f"Bearer {admin_token}"},
json={"name": "Widget", "price": 10.00, "category": "weapons"}
)
assert response.status_code == 400
@pytest.mark.parametrize("category", [
"electronics", "clothing", "food", "other"
])
def test_update_valid_category_values(self, admin_token, seed_product, category):
"""200 for each valid enum value."""
response = httpx.put(
f"{BASE_URL}/api/v2/products/{seed_product['id']}",
headers={"Authorization": f"Bearer {admin_token}"},
json={"name": "Widget", "price": 10.00, "category": category}
)
assert response.status_code == 200
assert response.json()["category"] == category
Аннотация: что делает эти тесты хорошими
1. Настройка и очистка на основе фикстур
Фикстура seed_product создаёт тестовые данные и очищает их после завершения. Это обеспечивает изоляцию тестов -- ни один тест не зависит от состояния предыдущего.
2. Многоуровневые утверждения
Хорошие тесты проверяют не только код статуса:
# Not just: assert response.status_code == 200
# But also:
assert body["id"] == seed_product["id"] # Correct resource returned
assert body["category"] in [...] # Valid enum
assert body["price"] >= 0 # Business constraint
3. Параметризованные граничные тесты
Вместо написания отдельных тестовых функций для каждого граничного значения, параметризация объединяет их в компактную, удобную для поддержки структуру.
4. Проверка валидных и невалидных границ
Обратите внимание на ДВА параметризованных теста для длины имени:
test_update_name_boundaryтестирует НЕВАЛИДНЫЕ значения (ожидается 400)test_update_name_valid_boundariesтестирует ВАЛИДНЫЕ значения (ожидается 200)
Это обеспечивает покрытие обеих сторон границы.
5. Каждый документированный код статуса имеет тест
Схема документирует 200, 400, 401 и 403. Для каждого есть хотя бы один тест, вызывающий этот код.
Типичные улучшения после генерации
| Проблема | Как исправить |
|---|---|
| BASE_URL захардкожен | Вынести в переменную окружения или фикстуру pytest |
get_test_token не определён |
Создать фикстуру conftest.py или вспомогательный модуль |
| Отсутствует таймаут в HTTP-вызовах | Добавить timeout=10.0 во все вызовы httpx |
| Нет утверждений о сообщениях ошибок | Добавить assert "name" in response.json()["detail"] для ответов 400 |
| Отсутствует заголовок Content-Type | Добавить "Content-Type": "application/json" в заголовки |
| Нет теста на семантику PATCH vs PUT | Если оба существуют, проверить что PUT заменяет, а PATCH обновляет |
Ключевой вывод
Тесты API, сгенерированные ИИ из OpenAPI-схем, получаются на удивление качественными, если промпт включает полную схему с разрешёнными $ref, указывает фреймворк и механизм аутентификации, и запрашивает конкретные категории тестов (happy path, ошибки, границы, аутентификация). Основная задача при ревью -- убедиться, что фикстуры и хелперы существуют в вашей кодовой базе и что утверждения о сообщениях ошибок достаточно специфичны.