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**: відсутні обов'язкові поля → 422, неправильні типи → 422
5. **Граничні значення**: min/max довжина рядків, числові межі
6. **Business rules**: специфічні правила з опису endpoint
Використовуй: pytest + httpx + jsonschema
Base 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, P95 latency < 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 дні на regression-тестування API перед кожним релізом. Сгенерували 180 contract-тестів з OpenAPI spec та 60 security-тестів. Тести виявили: 2 endpoint'и без auth-перевірки, 1 SQL-injection у фільтрі звітів, некоректна обробка Unicode в іменах (500 замість 422).
Строки: генератор з OpenAPI + contract-тести: 2–3 тижні; security та performance тести: 3–4 тижні дополнительно.







