Modern QA2026Версионирование API
Join

Course14 API Testing Fundamentals

Foundations · Chapter 14

Версионирование API

Updated Jul 2026

Версионирование API — это способ, которым команды развивают свои API без нарушения работы существующих клиентов. Как QA-инженер, вы должны тестировать, что новые версии работают корректно И что старые версии продолжают функционировать. Нарушение обратной совместимости — один из наиболее критичных багов, которые вы можете обнаружить.

Стратегии версионирования

Стратегия Пример Плюсы Минусы
URL-путь /v1/users, /v2/users Наглядно, легко маршрутизировать Засорение URL, сложно удалять старые версии
Заголовок Accept: application/vnd.api+json; version=2 Чистые URL Сложнее тестировать вручную, легко пропустить
Query-параметр /users?version=2 Просто Засорение URL, сложности с кешированием
Согласование контента Accept: application/vnd.company.v2+json RESTful Сложно реализовать и тестировать

Версионирование через URL-путь (наиболее распространённое)

def test_v1_users_endpoint(api):
    r = api.get("/v1/users")
    assert r.status_code == 200
    user = r.json()["items"][0]
    assert "name" in user  # v1 returns "name"

def test_v2_users_endpoint(api):
    r = api.get("/v2/users")
    assert r.status_code == 200
    user = r.json()["items"][0]
    assert "first_name" in user  # v2 splits into first/last
    assert "last_name" in user

Версионирование через заголовок

def test_header_versioning(api, base_url):
    # Default version (no header)
    r = api.get("/users")
    assert r.status_code == 200
    default_fields = set(r.json()["items"][0].keys())

    # Explicit v2
    r = api.get("/users", headers={"API-Version": "2"})
    assert r.status_code == 200
    v2_fields = set(r.json()["items"][0].keys())

    # v2 should have new fields
    assert "first_name" in v2_fields
    assert "last_name" in v2_fields

def test_missing_version_header_uses_default(api):
    """When no version header is sent, the API should use the default version."""
    r = api.get("/users")
    assert r.status_code == 200
    # Verify it returns the default version's response format

Тестирование обратной совместимости

Наиболее важный аспект тестирования версионирования API: старые клиенты не должны ломаться при выпуске новых версий.

def test_v1_still_works_after_v2_launch(api):
    """v1 endpoints must continue to function correctly."""
    # Create a user via v1
    r = api.post("/v1/users", json={"name": "Alice", "email": "alice@test.com"})
    assert r.status_code == 201

    # Read via v1
    user_id = r.json()["id"]
    r = api.get(f"/v1/users/{user_id}")
    assert r.status_code == 200
    assert r.json()["name"] == "Alice"

    # Delete via v1
    r = api.delete(f"/v1/users/{user_id}")
    assert r.status_code == 204

def test_v1_data_accessible_via_v2(api):
    """Data created in v1 should be accessible in v2 format."""
    # Create in v1 format
    r = api.post("/v1/users", json={"name": "Bob Smith", "email": "bob@test.com"})
    user_id = r.json()["id"]

    # Read in v2 format
    r = api.get(f"/v2/users/{user_id}")
    assert r.status_code == 200
    # v2 should correctly split the name
    assert r.json()["first_name"] == "Bob"
    assert r.json()["last_name"] == "Smith"

def test_v2_data_accessible_via_v1(api):
    """Data created in v2 should be accessible in v1 format."""
    r = api.post("/v2/users", json={
        "first_name": "Jane",
        "last_name": "Doe",
        "email": "jane@test.com"
    })
    user_id = r.json()["id"]

    # Read in v1 format
    r = api.get(f"/v1/users/{user_id}")
    assert r.status_code == 200
    assert r.json()["name"] == "Jane Doe"  # v1 should combine first/last

Тестирование устаревших эндпоинтов

def test_deprecated_endpoint_returns_warning(api):
    """Deprecated endpoints should include a deprecation warning header."""
    r = api.get("/v1/legacy-endpoint")
    assert r.status_code == 200  # Still works
    # Should include deprecation warning
    assert "Deprecation" in r.headers or "Sunset" in r.headers or \
        "X-Deprecated" in r.headers

def test_deprecated_endpoint_still_functional(api):
    """Deprecated endpoints must still work until officially removed."""
    r = api.get("/v1/legacy-endpoint")
    assert r.status_code == 200
    assert len(r.json()["items"]) > 0  # Still returns data

Типичные баги версионирования

Баг Стратегия тестирования
v1 ломается после деплоя v2 Запустите полный набор тестов v1 после деплоя v2
Данные, созданные в v1, не видны в v2 Кросс-версионные CRUD-тесты
Версия по умолчанию неожиданно меняется Тестируйте запросы без явного указания версии
Ответы об ошибках различаются между версиями Проверьте, что формат ошибок консистентен
Устаревший эндпоинт перестаёт работать до даты sunset Мониторьте устаревшие эндпоинты
v2 возвращает поля формата v1 Строго проверяйте схему ответа v2

Обнаружение версий

def test_api_exposes_available_versions(api, base_url):
    """API should document available versions."""
    r = api.get("/")
    if r.status_code == 200:
        data = r.json()
        # Some APIs list available versions at the root
        if "versions" in data:
            assert "v1" in data["versions"]
            assert "v2" in data["versions"]

def test_unsupported_version_returns_error(api, base_url):
    """Requesting a non-existent version should return a clear error."""
    r = api.get("/v99/users")
    assert r.status_code in (400, 404)

Практическое упражнение

  1. Напишите тесты обратной совместимости: создайте ресурсы в v1, проверьте доступность в v2
  2. Напишите кросс-версионные тесты данных: создайте в v2, прочитайте в v1
  3. Протестируйте поведение версии по умолчанию (без указания версии)
  4. Протестируйте устаревшие эндпоинты: проверьте, что они работают и включают заголовки устаревания
  5. Напишите тестовую матрицу, покрывающую все CRUD-операции для всех версий API

Ключевые выводы

  • Всегда тестируйте обратную совместимость: v1 не должен ломаться при запуске v2
  • Тестируйте кросс-версионный доступ к данным: данные, созданные в одной версии, должны быть читаемы в другой
  • Тестируйте поведение версии по умолчанию: что происходит, когда версия не указана?
  • Мониторьте устаревшие эндпоинты: они должны работать до объявленной даты sunset
  • Версионирование через URL-путь наиболее распространено; версионирование через заголовок чище, но сложнее тестировать вручную
  • Тестирование совместимости версий предотвращает один из наиболее критичных типов API-багов

Тезис для собеседования: «Я строю наборы API-тестов в pytest с библиотекой requests, организованные вокруг аутентификации, CRUD-операций, обработки ошибок и граничных случаев, таких как ограничение частоты запросов и пагинация. Я тестирую как успешные пути, так и режимы отказа — просроченные токены, некорректные payload, утечку информации в ответах об ошибках. Я параметризую окружения, чтобы один и тот же набор тестов работал в dev, staging и production при изменении одной переменной.»