Повторный запрос — не повторная операция: как QA тестировать retries
Кнопка оплаты нажата один раз, а заказ создан дважды. Пользователь видит таймаут и нажимает ещё раз. Клиент сам повторяет запрос, потому что не получил ответ. Сервер успел выполнить операцию, но ответ потерялся по дороге.
Для QA это не один сценарий «проверить retry». Нужно понять, что именно можно повторять, кто отвечает за повтор и как система отличает новую операцию от продолжения старой.
Сначала зафиксировать контракт
Для каждого запроса запишите: может ли он повторяться, кто выполняет повтор, сколько попыток допустимо, какие ошибки считаются временными и что увидит пользователь после исчерпания попыток.
GET обычно безопасно повторить, но даже чтение может запускать побочный эффект в плохо спроектированном endpoint. POST не становится безопасным от повторения только потому, что клиент добавил таймаут. Для создания заказа, платежа или выдачи бонуса нужен отдельный механизм идемпотентности.
Ожидаемый результат должен описывать состояние на сервере, а не только HTTP-код. После повторной отправки с тем же ключом должна остаться одна операция, а клиент должен получить согласованный результат этой операции.
Разделить причины повторной попытки
Проверьте отдельно сетевой обрыв до отправки, обрыв после отправки, таймаут ожидания, временный ответ 502/503, ограничение скорости 429 и явную бизнес-ошибку. Повторять все ошибки одинаково опасно: неверный пароль или недостаток средств не исправятся ещё одной попыткой.
Для каждой причины проверьте число фактических запросов, интервалы между ними и заголовки. Система может повторить запрос на уровне SDK, HTTP-клиента, прокси и очереди — тогда заявленные три попытки превращаются в двенадцать.
Добавьте управляемую задержку на тестовом сервере. Она помогает воспроизвести случай, когда сервер уже изменил состояние, а клиент ещё считает запрос неуспешным.
Проверить идемпотентность
Отправьте одну и ту же операцию несколько раз с одинаковым idempotency key. Проверьте, что создаётся одна сущность, списание происходит один раз, а повторный ответ указывает на тот же результат.
Затем повторите запрос с тем же ключом, но изменённым телом. Сервер должен либо вернуть исходный результат, либо явно отклонить конфликт. Молчаливое выполнение второй операции создаёт трудноуловимый дефект.
Проверьте пустой ключ, слишком длинный ключ, разные регистры и срок его хранения, если эти ограничения есть в контракте. Зафиксируйте, что происходит после истечения срока: новая операция может быть корректной, но это должно быть документировано.
Проверить порядок и состояние интерфейса
Во время повтора пользователь не должен получить два параллельных состояния: «оплата прошла» и «не удалось». Проверьте блокировку кнопки, индикатор выполнения, повторное открытие экрана и восстановление после перезапуска приложения.
Смоделируйте ответы в разном порядке: поздний ответ первой попытки приходит после успешного ответа повтора. UI не должен откатывать подтверждённое состояние старым результатом.
Для офлайн-очереди проверьте, не отправляется ли одна операция повторно после восстановления сети. Смена аккаунта до отправки накопленной очереди заслуживает отдельного сценария: событие пользователя A не должно уйти от имени пользователя B.
Проверить наблюдаемость
В логах и метриках различайте request_id, operation_id и номер попытки. Одного correlation_id недостаточно, если по нему невозможно понять, были ли это три повтора одной операции или три разные операции.
В алертах учитывайте итог операции, а не только количество HTTP-ответов. Пять временных ошибок с одним успешным результатом — другой сигнал, чем пять созданных заказов.
Положительный контроль важен и здесь: тест должен подтвердить доставку события и сопоставить его с конкретной операцией. Отсутствие дубликата в пустом или недоставленном логе не доказывает идемпотентность.
Минимальный набор перед релизом
- таймаут до отправки и после выполнения на сервере;
- 502, 503, 429 и бизнес-ошибка;
- одинаковый idempotency key при одинаковом и изменённом теле;
- повтор после перезапуска и восстановления сети;
- поздний ответ старой попытки;
- фактическое число запросов на каждом уровне клиента;
- одна операция в хранилище и согласованное состояние интерфейса.
Критерий успеха простой: повтор запроса не меняет результат операции неожиданным образом, а команда может по журналам восстановить, сколько попыток было сделано и чем они закончились.