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