Сценарий оформления заказа повторно запускается после частичного сбоя и создаёт дубликаты. Какое свойство теста нужно обеспечить, чтобы повторный запуск не менял смысл результата?
Тест должен быть идемпотентным: его повторный запуск в тех же условиях не должен создавать дополнительные побочные эффекты или изменять ожидаемый смысл результата. Для этого тестовые операции нужно выполнять с уникальным идентификатором сценария, безопасно повторять либо предварительно удалять созданные сущности.
Идемпотентность не означает, что каждый запуск обязан оставить базу полностью неизменной. Она означает, что повторное выполнение не приводит к неконтролируемому накоплению данных, дубликатам и зависимости результата от числа попыток.
В автоматизации тестирования тесты часто перезапускают после временных сбоев инфраструктуры, сетевых ошибок или падения самого тестового процесса. Если повторный запуск повторяет необратимую операцию без защиты, тест начинает проверять не только продукт, но и последствия собственной предыдущей попытки.
Идея идемпотентности возникла как общий способ безопасно повторять операции в распределённых системах, где клиент не всегда знает, дошёл ли запрос до сервера. В тестах тот же принцип помогает сделать повторный запуск предсказуемым и снизить число ложных падений.
Предположим, тест создаёт заказ, затем проверяет его оплату. Сбой может произойти после создания заказа, но до проверки результата. При повторном запуске тест создаст второй заказ, а проверка может найти первый, получить конфликт уникальности или оставить после себя неконтролируемые данные.
Без идемпотентности возникают несколько рисков:
Важно отличать проблему теста от требования продукта. Если бизнес-операция по смыслу не должна выполняться дважды, тест должен одновременно проверять защиту продукта от дубликатов, но сам сценарий обязан однозначно различать созданные им данные.
Основной механизм — уникальный идентификатор бизнес-операции или тестового запуска. Все связанные сущности, например заказ и платёж, создаются с этим идентификатором, а повторная попытка либо находит уже существующий результат, либо безопасно продолжает обработку вместо создания дубликата.
Идемпотентность можно обеспечить несколькими способами:
На уровне тестовой архитектуры идентификатор должен формироваться один раз для сценария и передаваться во все операции, относящиеся к одной бизнес-транзакции. Нельзя использовать случайные значения без сохранения seed или идентификатора: такой тест может избежать конфликта, но не даст воспроизводимо продолжить незавершённую попытку.
Идемпотентность имеет ограничения. Она не должна скрывать дефект, если повторная отправка действительно обязана приводить к ошибке. В таком случае тест должен явно проверять корректное поведение при повторе, а не безусловно добиваться успешного результата.
Также нужно учитывать область действия ключа. Слишком короткий срок его хранения может позволить создать дубликат после истечения срока, а глобальный ключ без привязки к тестовому запуску создаст конфликты между параллельными прогонами.
Команда тестировала оформление заказа через API. После сетевого тайм-аута тест запускался повторно, и в системе появлялись два заказа с одинаковыми товаром и пользователем. Простое удаление всех заказов перед тестом оказалось опасным: параллельные сценарии могли удалить чужие данные.
Рассматривались три варианта. Полная очистка базы была простой, но неприемлемой для общего стенда. Уникальные случайные данные снижали конфликты, но не решали проблему повторной отправки одной операции. Повторное использование ключа операции позволяло серверу распознать повтор и вернуть уже созданный результат.
Выбрали третий вариант: каждый запуск получал уникальный идентификатор, который сохранялся при сбое и использовался при retry. Сервер обрабатывал повтор с тем же ключом как продолжение той же операции, а тест дополнительно проверял отсутствие второго заказа. В результате повторные прогоны перестали порождать дубликаты, а защита продукта от повторной оплаты стала явно проверяемой.
Нет. Очистка помогает вернуть окружение в исходное состояние после успешного запуска, но не защищает от частичного сбоя, когда тест остановился до этапа очистки. Кроме того, очистка может быть небезопасной при параллельном выполнении. Идемпотентность должна обеспечивать безопасное повторение самих операций, а очистка остаётся отдельным механизмом управления тестовыми данными.
Retry лишь запускает тест ещё раз и не делает его безопасным. Если первый запуск успел изменить состояние, второй может получить другой результат именно из-за этого изменения. Идемпотентный тест допускает повторение без неконтролируемого накопления эффектов; retry только использует это свойство, но не создаёт его.
Если при каждой попытке создаётся новый идентификатор операции, система воспринимает повтор как новую бизнес-операцию и не может предотвратить дубликат. Для независимых тестовых запусков идентификаторы действительно должны различаться, но повторные попытки одного сценария должны использовать один и тот же ключ операции. Поэтому необходимо различать идентификатор запуска и идентификатор повторяемой бизнес-операции.