Что такое идемпотентность?

«Что такое идемпотентность?» — вопрос из категории HTTP и веб-протоколы, который задают на 35% собеседований AQA / Automation. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Почему это критически важно для QA-инженера?

  1. Отказоустойчивость и повторные запросы: Клиент (или тест) может безопасно повторять идемпотентный запрос при таймаутах, сетевых сбоях, не получая побочных эффектов (например, создания дубликатов).
  2. Предсказуемость тестов: Идемпотентные операции делают тесты более стабильными и воспроизводимыми.
Идемпотентность HTTP-методов (с точки зрения тестировщика): Метод Идемпотентен? Объяснение и что проверять
GET Да Получение данных. 10 одинаковых GET /users/1 должны вернуть один и тот же ответ (если данные не менялись иным способом). Проверяем кэширование.
POST Нет Создание ресурса. Повторный POST /users с телом {"name":"John"} создаст второго пользователя с тем же именем (дубликат). Это частая причина багов.
PUT Да Полное обновление. PUT /users/1 с телом {"name":"Alice"}. Сколько бы раз мы его ни отправили, в итоге у пользователя с id=1 будет имя "Alice". Проверяем, что последний запрос "побеждает".
DELETE Да Удаление. Первый DELETE /users/1 удалит пользователя и вернет 200 или 204. Последующие вызовы должны возвращать 404 (Not Found) или 410 (Gone) — результат разный, но состояние системы не меняется после первого вызова. Это все равно идемпотентность.
PATCH Нет (обычно) Частичное обновление. Зависит от реализации. PATCH /users/1 с {"balance": +10} при каждом вызове будет увеличивать баланс на 10. Это неидемпотентно.

Пример теста на проверку идемпотентности метода PUT:

// Пример на Java с RestAssured
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Test
public void testPutMethodIsIdempotent() {
    String userJson = "{"name": "TestUser", "email": "test@example.com"}";

    // 1. Первый PUT-запрос (создание/обновление)
    given()
        .contentType("application/json")
        .body(userJson)
    .when()
        .put("/api/users/123")
    .then()
        .statusCode(200) // или 201 Created
        .body("name", equalTo("TestUser"));

    // 2. Второй ИДЕНТИЧНЫЙ PUT-запрос
    given()
        .contentType("application/json")
        .body(userJson) // То же самое тело
    .when()
        .put("/api/users/123")
    .then()
        .statusCode(200) // Должен быть тот же статус, что и в первый раз (не 201)
        .body("name", equalTo("TestUser")); // Имя должно остаться "TestUser", а не измениться

    // 3. Дополнительная проверка: GET после двух PUT должен вернуть те же данные
    when()
        .get("/api/users/123")
    .then()
        .body("name", equalTo("TestUser"));
}

Как тестировать неидемпотентные операции (например, POST)?

  1. Использовать уникальные данные (timestamp, UUID) в каждом тестовом прогоне.
  2. Реализовать механизм очистки тестовых данных перед/после теста.
  3. Проверять, что сервер возвращает корректные ошибки при попытке создать дубликат (например, 409 Conflict).

Видео-ответы