TL;DR

  • API тестирование проверяет backend-сервисы без UI — быстрее и надежнее E2E тестов
  • Тестируй: статус-коды, тело ответа, заголовки, обработку ошибок, аутентификацию, валидацию схем, производительность
  • Инструменты: Postman (ручное/обучение), REST Assured (Java), Supertest (Node.js), requests (Python)
  • Автоматизируй в CI/CD — API часто меняются, ловите breaking changes рано
  • Покрывай happy path и сценарии ошибок (400, 401, 404, 500)
  • Валидируй схемы ответов для предотвращения дрифта контрактов между фронтом и бэком

Подходит для: Backend-разработчиков, QA-инженеров, всех, кто тестирует микросервисы Пропусти, если: Тестируешь только статические сайты или простые фронтенды

Твои фронтенд-тесты проходят. Пользователи сообщают, что приложение сломано. API изменился, и никто не протестировал контракт.

API тестирование ловит такие проблемы до того, как они дойдут до пользователей. Это быстрее UI-тестирования, надежнее и тестирует реальную бизнес-логику, от которой зависит твое приложение.

Я десятки раз ловил ломающие изменения в API контрактах — переименованное поле, измененный статус-код, пропавший заголовок пагинации. Каждый из них вызвал бы даунтайм, если бы не был пойман API тестами в CI.

Этот туториал учит API тестированию с нуля — основы HTTP, REST-конвенции, аутентификация, обработка ошибок, валидация схем и автоматизация.

Что такое API тестирование?

API (Application Programming Interface) тестирование проверяет, что твои backend-сервисы работают корректно. Вместо кликов через UI ты отправляешь HTTP-запросы напрямую к endpoints и проверяешь ответы.

Что покрывает API тестирование:

  • Функциональность — делает ли endpoint то, что должен?
  • Валидация данных — структурированы ли ответы правильно?
  • Обработка ошибок — падает ли gracefully?
  • Аутентификация — контролируется ли доступ?
  • Соответствие схеме — соответствует ли ответ контракту?
  • Производительность — выдерживает ли нагрузку?

Почему API тестирование важно:

  • Быстрее UI-тестов — нет рендеринга браузера, миллисекунды vs секунды
  • Стабильнее — нет flaky селекторов или проблем тайминга
  • Раннее обнаружение — тестируй до существования фронтенда
  • Лучшее покрытие — тестируй edge cases, невозможные через UI

Где API тесты в пирамиде тестирования:

        /  UI тесты   \          ← Медленные, дорогие, мало
       / Интеграционные \         ← API тесты живут здесь
      /  Unit тесты     \        ← Быстрые, дешевые, много

API тесты — золотая середина: достаточно быстрые для запуска на каждом коммите, но достаточно глубокие для поиска реальных интеграционных багов.

Основы HTTP

Перед тестированием API пойми основы HTTP.

HTTP методы

GET     /users          # Получить всех пользователей
GET     /users/123      # Получить пользователя 123
POST    /users          # Создать нового пользователя
PUT     /users/123      # Заменить пользователя 123
PATCH   /users/123      # Обновить части пользователя 123
DELETE  /users/123      # Удалить пользователя 123
МетодНазначениеЕсть телоИдемпотентный
GETЧтение данныхНетДа
POSTСоздание ресурсаДаНет
PUTЗамена ресурсаДаДа
PATCHЧастичное обновлениеДаНет
DELETEУдаление ресурсаОпциональноДа

Идемпотентность важна для тестирования: Вызов GET или PUT 5 раз даст тот же результат, что и один вызов. POST создает новый ресурс каждый раз. Тесты должны это учитывать — PUT тесты можно ретраить, POST тесты нуждаются в очистке.

Статус-коды

2xx Успех
├── 200 OK              # Запрос успешен
├── 201 Created         # Ресурс создан
├── 204 No Content      # Успех, нечего возвращать

4xx Ошибки клиента
├── 400 Bad Request     # Невалидный ввод
├── 401 Unauthorized    # Отсутствует/невалидная авторизация
├── 403 Forbidden       # Валидная авторизация, нет разрешения
├── 404 Not Found       # Ресурс не существует
├── 409 Conflict        # Конфликт с текущим состоянием
├── 422 Unprocessable   # Валидация провалена
├── 429 Too Many Req    # Rate limit превышен

5xx Ошибки сервера
├── 500 Internal Error  # Баг сервера
├── 502 Bad Gateway     # Ошибка upstream
├── 503 Unavailable     # Сервер перегружен/обслуживание

Частая ошибка: Проверять только 200. Всегда верифицируй конкретный ожидаемый код — 200 вместо 201 означает проблему.

Тестирование с Postman

Postman — самый простой способ начать API тестирование.

Первый запрос

  1. Открой Postman
  2. Введи URL: https://jsonplaceholder.typicode.com/posts/1
  3. Метод: GET
  4. Нажми Send

Добавление тестов

В Postman добавь JavaScript тесты во вкладке “Tests”:

// Проверка статус-кода
pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

// Время ответа
pm.test("Response time is less than 500ms", function () {
    pm.expect(pm.response.responseTime).to.be.below(500);
});

// Валидация тела
pm.test("Has correct structure", function () {
    const json = pm.response.json();
    pm.expect(json).to.have.property("id");
    pm.expect(json).to.have.property("title");
    pm.expect(json.id).to.eql(1);
});

Коллекции, переменные и цепочки запросов

// Переменные окружения
pm.environment.set("baseUrl", "https://api.example.com");

// Цепочки: извлечь токен из логина, использовать в следующем запросе
pm.test("Save auth token", function () {
    const response = pm.response.json();
    pm.environment.set("token", response.token);
    pm.environment.set("userId", response.user.id);
});

Запуск коллекций из CLI с Newman:

npm install -g newman
newman run collection.json -e production.json --reporters cli,html

REST API тестирование с кодом

Python с requests + pytest

import requests
import pytest

BASE_URL = "https://api.example.com"

class TestUsersAPI:

    def test_get_users_returns_list(self):
        response = requests.get(f"{BASE_URL}/users")

        assert response.status_code == 200
        assert isinstance(response.json(), list)
        assert len(response.json()) > 0

    def test_create_user(self):
        payload = {
            "name": "John Doe",
            "email": "john@example.com"
        }

        response = requests.post(
            f"{BASE_URL}/users",
            json=payload,
            headers={"Content-Type": "application/json"}
        )

        assert response.status_code == 201
        data = response.json()
        assert data["name"] == payload["name"]
        assert "id" in data

    def test_get_nonexistent_user(self):
        response = requests.get(f"{BASE_URL}/users/99999")
        assert response.status_code == 404

    def test_create_user_invalid_email(self):
        payload = {"name": "John", "email": "not-an-email"}
        response = requests.post(f"{BASE_URL}/users", json=payload)
        assert response.status_code == 400

JavaScript с Supertest

const request = require('supertest');
const app = require('../src/app');

describe('Users API', () => {
  test('GET /users returns list of users', async () => {
    const response = await request(app)
      .get('/users')
      .expect(200)
      .expect('Content-Type', /json/);

    expect(Array.isArray(response.body)).toBe(true);
  });

  test('POST /users creates new user', async () => {
    const newUser = { name: 'John Doe', email: 'john@example.com' };

    const response = await request(app)
      .post('/users')
      .send(newUser)
      .expect(201);

    expect(response.body).toMatchObject(newUser);
    expect(response.body.id).toBeDefined();
  });
});

Java с REST Assured

import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.*;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

public class UsersApiTest {

    @BeforeAll
    public static void setup() {
        RestAssured.baseURI = "https://api.example.com";
    }

    @Test
    public void getUsersReturnsList() {
        given()
            .when()
                .get("/users")
            .then()
                .statusCode(200)
                .contentType(ContentType.JSON)
                .body("size()", greaterThan(0));
    }

    @Test
    public void createUserReturnsCreated() {
        String requestBody = """
            {
                "name": "John Doe",
                "email": "john@example.com"
            }
            """;

        given()
            .contentType(ContentType.JSON)
            .body(requestBody)
        .when()
            .post("/users")
        .then()
            .statusCode(201)
            .body("name", equalTo("John Doe"))
            .body("id", notNullValue());
    }
}

Валидация схем ответов

Тестирование отдельных полей недостаточно. Валидируй всю структуру ответа, чтобы поймать дрифт контракта.

JSON Schema валидация с Python

from jsonschema import validate

user_schema = {
    "type": "object",
    "required": ["id", "name", "email", "createdAt"],
    "properties": {
        "id": {"type": "integer"},
        "name": {"type": "string", "minLength": 1},
        "email": {"type": "string", "format": "email"},
        "createdAt": {"type": "string", "format": "date-time"},
        "role": {"type": "string", "enum": ["admin", "user", "viewer"]}
    },
    "additionalProperties": False
}

def test_user_response_matches_schema():
    response = requests.get(f"{BASE_URL}/users/1")
    validate(instance=response.json(), schema=user_schema)

Почему валидация схемы важна: Разработчик добавляет поле, переименовывает другое или меняет тип. Точечные проверки это пропускают. Валидация схемы ловит каждое структурное изменение — это страж твоего API контракта.

Тестирование аутентификации

Bearer Token (JWT)

# Шаг 1: Логин для получения токена
login_response = requests.post(
    "https://api.example.com/auth/login",
    json={"email": "user@example.com", "password": "secret"}
)
token = login_response.json()["token"]

# Шаг 2: Использование токена в запросах
response = requests.get(
    "https://api.example.com/protected",
    headers={"Authorization": f"Bearer {token}"}
)

Тестирование сценариев авторизации

class TestAuthentication:

    def test_protected_endpoint_requires_auth(self):
        response = requests.get(f"{BASE_URL}/protected")
        assert response.status_code == 401

    def test_invalid_token_rejected(self):
        response = requests.get(
            f"{BASE_URL}/protected",
            headers={"Authorization": "Bearer invalid_token"}
        )
        assert response.status_code == 401

    def test_valid_token_grants_access(self):
        token = get_valid_token()
        response = requests.get(
            f"{BASE_URL}/protected",
            headers={"Authorization": f"Bearer {token}"}
        )
        assert response.status_code == 200

    def test_insufficient_permissions(self):
        viewer_token = get_token_for_role("viewer")
        response = requests.delete(
            f"{BASE_URL}/users/1",
            headers={"Authorization": f"Bearer {viewer_token}"}
        )
        assert response.status_code == 403

Тестирование GraphQL

GraphQL использует один endpoint с queries и mutations. Тестирование принципиально отличается от REST.

def test_graphql_query():
    query = """
    query GetUser($id: ID!) {
        user(id: $id) { id, name, email }
    }
    """

    response = requests.post(
        f"{BASE_URL}/graphql",
        json={"query": query, "variables": {"id": "123"}}
    )

    assert response.status_code == 200
    data = response.json()
    assert "errors" not in data
    assert data["data"]["user"]["id"] == "123"

def test_graphql_invalid_query():
    """GraphQL возвращает 200 с массивом errors, не 400"""
    response = requests.post(
        f"{BASE_URL}/graphql",
        json={"query": "{ nonExistentField }"}
    )
    assert response.status_code == 200
    assert "errors" in response.json()

Ключевое отличие от REST: GraphQL всегда возвращает 200 для валидных HTTP запросов. Ошибки — в массиве errors внутри тела ответа.

Тестирование ошибок

Тестируй, как API обрабатывает проблемы. Здесь живет большинство багов.

class TestErrorHandling:

    def test_malformed_json_returns_400(self):
        response = requests.post(
            f"{BASE_URL}/users",
            data="not valid json",
            headers={"Content-Type": "application/json"}
        )
        assert response.status_code == 400

    def test_missing_required_field_returns_400(self):
        response = requests.post(
            f"{BASE_URL}/users",
            json={"name": "John"}  # отсутствует email
        )
        assert response.status_code == 400

    def test_duplicate_email_returns_409(self):
        requests.post(f"{BASE_URL}/users", json={
            "name": "John", "email": "john@example.com"
        })
        response = requests.post(f"{BASE_URL}/users", json={
            "name": "Jane", "email": "john@example.com"
        })
        assert response.status_code == 409

Тестирование Rate Limiting

Большинство API в продакшене имеют ограничения скорости. Тестируй их.

def test_rate_limiting():
    response = requests.get(f"{BASE_URL}/users")
    assert "X-RateLimit-Limit" in response.headers
    assert "X-RateLimit-Remaining" in response.headers

def test_rate_limit_exceeded():
    for _ in range(110):  # Если лимит 100 запросов/мин
        requests.get(f"{BASE_URL}/users")

    response = requests.get(f"{BASE_URL}/users")
    assert response.status_code == 429
    assert "Retry-After" in response.headers

Performance тестирование

Load тестирование с k6

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '30s', target: 20 },
    { duration: '1m', target: 20 },
    { duration: '10s', target: 0 },
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],
    http_req_failed: ['rate<0.01'],
  },
};

export default function () {
  const response = http.get('https://api.example.com/users');
  check(response, {
    'status is 200': (r) => r.status === 200,
    'response time OK': (r) => r.timings.duration < 500,
  });
  sleep(1);
}

Интеграция CI/CD

name: API Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: '3.12' }
      - run: pip install pytest requests jsonschema
      - run: pytest tests/api/ -v --tb=short
      - run: |
          npm install -g newman
          newman run collection.json -e environment.json

ИИ в API тестировании

ИИ-инструменты значительно ускоряют разработку API тестов в 2026.

Что ИИ делает хорошо:

  • Генерация тест-кейсов из OpenAPI/Swagger спецификаций — вставь спеку, получи 50+ тестов
  • Создание валидных и невалидных тестовых данных для граничного тестирования
  • Написание boilerplate для аутентификации и CRUD операций
  • Конвертация между фреймворками (скрипты Postman в pytest, REST Assured в Supertest)

Что всё ещё требует людей:

  • Понимание бизнес-требований и доменных ограничений
  • Проектирование стратегии тестирования (какие endpoints критичны?)
  • Отладка flaky тестов из-за зависимостей данных
  • Интерпретация результатов производительности

Полезный промпт:

Сгенерируй pytest тест-кейсы для POST /api/orders с телом { customerId, items: [{productId, quantity}], couponCode? }. Покрой: валидный заказ, пустой массив items, невалидный customerId, отрицательное количество, невалидный купон. Включи JSON Schema валидацию.

FAQ

Что такое API тестирование?

API тестирование проверяет, что API работает корректно, отправляя HTTP запросы и валидируя ответы. Тестирует функциональность, валидацию данных, обработку ошибок, аутентификацию и производительность. В отличие от UI тестирования, API тестирование напрямую проверяет слой бизнес-логики — типичный API тест занимает миллисекунды против секунд для браузерного теста.

Какие инструменты используются для API тестирования?

Популярные инструменты:

  • Postman — GUI для ручного тестирования и автоматизации, лучший для обучения
  • REST Assured — Java-библиотека с BDD-стилем
  • Supertest — Node.js/JavaScript API тестирование, интеграция с Jest
  • requests + pytest — Python API тестирование, максимальная гибкость
  • k6 — Performance и load тестирование на JavaScript
  • Newman — CLI-раннер коллекций Postman для CI/CD
  • Karate — BDD-фреймворк, объединяющий API и performance тесты

В чем разница между API и unit тестированием?

Unit-тесты проверяют отдельные функции изолированно, мокая все зависимости. API-тесты проверяют полные HTTP endpoints, включая routing, middleware, аутентификацию, операции с БД и форматирование ответов. API тесты — интеграционные, проверяют работу компонентов вместе. Нужны оба: unit-тесты для логических багов, API тесты для интеграционных.

Как тестировать API с аутентификацией?

  1. Отправь login запрос с credentials
  2. Извлеки токен из ответа
  3. Включай токен в заголовок Authorization для последующих запросов
  4. Храни токен в переменной окружения для переиспользования
  5. Реализуй refresh токена для истекающих токенов
  6. Тестируй оба сценария — аутентифицированный и неаутентифицированный
  7. Тестируй ролевой доступ — viewer не может удалять, admin может

Чем тестирование REST API отличается от GraphQL?

REST использует множество endpoints (GET /users, POST /users) с фиксированной структурой ответа. GraphQL использует один endpoint (/graphql), где клиент запрашивает конкретные поля. Ключевые отличия: GraphQL возвращает 200 даже при ошибках (проверяй массив errors), нужно тестировать лимит глубины запросов (защита от DoS), и валидировать, что резолверы не создают N+1 запросы к БД.

Сколько API тестов нужно?

На каждый endpoint: 1 happy path тест, 2-3 негативных теста (400, 401, 404), 1 тест валидации схемы и 1 тест авторизации. Типичный CRUD ресурс (5 endpoints) нуждается в 15-20 тестах. Для API с 20 ресурсами — 300-400 тестов. Начинай с критичных для бизнеса endpoints — логин, оплата, основные данные — потом расширяй покрытие.

Официальные ресурсы

Смотрите также