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