AI-автоматизированное тестирование API
API-тестирование решает несколько задач одновременно: функциональность (корректные ответы), контрактное тестирование (schema validation), производительность (latency под нагрузкой), безопасность (auth, injection, rate limits). AI-система покрывает все четыре слоя, генерируя тесты из OpenAPI-спецификации и анализируя реальный трафик.
Генерация тестов из OpenAPI спецификации
import yaml
import json
from langchain_openai import ChatOpenAI
from pathlib import Path
class APITestGenerator:
CONTRACT_TEST_PROMPT = """Создай pytest тесты для API endpoint.
Endpoint: {method} {path}
OpenAPI Spec:
{spec}
Тесты должны покрыть:
1. **Happy path**: валидный запрос → ожидаемый ответ
2. **Schema validation**: ответ соответствует OpenAPI схеме (используй jsonschema)
3. **Auth**: запрос без токена → 401, с невалидным токеном → 401/403
4. **Validation errors**: отсутствующие required поля → 422, неверные типы → 422
5. **Граничные значения**: min/max длина строк, числовые пределы
6. **Business rules**: специфические правила из описания endpoint
Используй: pytest + httpx + jsonschema
Базовый URL через pytest fixture: base_url
Auth token через fixture: auth_token
Верни код тестов."""
def __init__(self):
self.llm = ChatOpenAI(model="gpt-4o", temperature=0.1)
def generate_from_openapi(self, spec_path: str) -> dict[str, str]:
"""Генерирует тесты для всех endpoints из OpenAPI spec"""
with open(spec_path) as f:
spec = yaml.safe_load(f)
test_files = {}
for path, methods in spec.get("paths", {}).items():
for method, endpoint_spec in methods.items():
test_code = self._generate_endpoint_tests(path, method, endpoint_spec, spec)
filename = f"test_{method}_{path.replace('/', '_').strip('_')}.py"
test_files[filename] = test_code
return test_files
def _generate_endpoint_tests(
self,
path: str,
method: str,
endpoint_spec: dict,
full_spec: dict
) -> str:
# Разрешаем $ref
resolved_spec = self._resolve_refs(endpoint_spec, full_spec)
return self.llm.invoke(
self.CONTRACT_TEST_PROMPT.format(
method=method.upper(),
path=path,
spec=json.dumps(resolved_spec, ensure_ascii=False, indent=2)
)
).content
Анализ реального трафика и генерация тестов
class TrafficBasedTestGenerator:
"""Генерирует тесты из HAR-файлов или прокси-логов"""
def generate_from_har(self, har_path: str) -> list[str]:
"""Генерирует regression-тесты из записанного трафика"""
with open(har_path) as f:
har = json.load(f)
entries = har["log"]["entries"]
api_calls = [
e for e in entries
if "api" in e["request"]["url"] or
e["response"]["content"].get("mimeType", "").startswith("application/json")
]
tests = []
for entry in api_calls[:50]: # топ-50 уникальных запросов
test = self._generate_regression_test(entry)
tests.append(test)
return tests
def _generate_regression_test(self, entry: dict) -> str:
request = entry["request"]
response = entry["response"]
prompt = f"""Создай pytest regression-тест из записанного HTTP-взаимодействия.
Request:
- Method: {request['method']}
- URL: {request['url']}
- Headers: {json.dumps({h['name']: h['value'] for h in request.get('headers', [])[:5]}, ensure_ascii=False)}
- Body: {request.get('postData', {}).get('text', '')[:500]}
Response:
- Status: {response['status']}
- Body: {response['content'].get('text', '')[:500]}
Создай тест который:
1. Воспроизводит запрос (с параметризованными тестовыми данными вместо реальных)
2. Проверяет статус-код
3. Проверяет схему ответа (ключи, типы)
4. Не хардкодит реальные данные (замени на fixtures)
Верни pytest код."""
return self.llm.invoke(prompt).content
Тестирование безопасности API
class APISecurityTester:
SECURITY_PROMPTS = {
"sql_injection": [
"' OR '1'='1", "'; DROP TABLE users;--",
"1 UNION SELECT NULL,NULL,NULL--",
"' AND SLEEP(5)--"
],
"nosql_injection": [
'{"$gt": ""}', '{"$where": "this.password.length > 0"}',
'{"$regex": ".*"}'
],
"xss": [
"<script>alert('xss')</script>",
"javascript:alert(1)",
'"><img src=x onerror=alert(1)>'
]
}
async def test_injection_resilience(
self,
endpoint: str,
param_name: str,
client
) -> list[dict]:
results = []
for attack_type, payloads in self.SECURITY_PROMPTS.items():
for payload in payloads:
response = await client.post(
endpoint,
json={param_name: payload}
)
# Приложение должно вернуть 400/422, а не 500 или данные
results.append({
"attack_type": attack_type,
"payload": payload,
"status": response.status_code,
"vulnerable": response.status_code == 500 or
self._contains_db_error(response.text)
})
return results
Нагрузочное тестирование через Locust
LOCUST_PROMPT = """Создай Locust нагрузочный тест для API.
Endpoints для нагрузки:
{endpoints}
Создай:
- HttpUser класс с task'ами для каждого endpoint
- Реалистичное распределение: частые операции → больший вес
- @task(3) для чтения, @task(1) для записи
- between(1, 5) для wait_time
- Обработка ошибок через on_failure
Цель: 100 RPS, latency P95 < 500 мс.
Верни Python код для locustfile.py."""
Конфигурация CI/CD
# Пирамида API-тестов в CI
api-tests:
contract:
run: pytest tests/api/contract/ -v
on: [push, pull_request]
security:
run: pytest tests/api/security/ -v
on: [pull_request]
performance:
run: locust -f tests/api/locustfile.py --headless -u 50 -r 5 --run-time 2m
on: [manual, schedule] # не блокируем PR
Кейс: REST API финтех-стартапа, 45 endpoints. Команда тратила 3 дня на регрессионное тестирование API перед каждым релизом. Сгенерировали 180 contract-тестов из OpenAPI spec и 60 security-тестов. Тесты обнаружили: 2 endpoint'а без auth-проверки, 1 SQL-injection в фильтре отчётов, некорректная обработка Unicode в именах (500 вместо 422).
Сроки: генератор из OpenAPI + contract-тесты: 2–3 недели; security и performance тесты: 3–4 недели дополнительно.







