automationtest-dataflaky-testscitesting

Тестовые данные в автотестах: источник флака и связности — и как их готовить

Когда автотесты флакают, первым делом грешат на локаторы и тайминги. Но один из самых частых и самых незаметных источников нестабильности — это тестовые данные. Тест, который зависит от записи в общей базе, рано или поздно упадёт: кто-то её поменял, удалил, или два параллельных теста подрались за один и тот же email.

Разберём, как готовить данные так, чтобы тесты были изолированными, детерминированными и переживали параллельный прогон.

Главная боль — общие изменяемые данные

Сценарий-классика: на стенде есть «пользователь [email protected]», и десяток тестов на него завязан. Один тест меняет ему профиль, другой удаляет, третий ждёт его в исходном состоянии. Пока тесты идут последовательно и в одном порядке — везёт. Включаете параллельный прогон или меняете порядок — каскад падений, которые невозможно воспроизвести локально.

Правило: тест не должен зависеть от данных, которые он не создавал сам. Любая общая изменяемая запись — это будущий флак.

Три способа получить данные (и когда что)

  • Фикстуры / seed-скрипты: заранее подготовленный набор. Хороши для справочников (страны, тарифы) — неизменяемых данных. Плохи для изменяемых сущностей, которые тест модифицирует.
  • Фабрики / билдеры: код создаёт нужную сущность в нужном состоянии прямо в тесте (Object Mother, Test Data Builder). Гибко, читаемо: aUser().withSubscription(EXPIRED).build(). Лучший дефолт для большинства тестов.
  • Setup через API: создавать предусловия не через UI, а вызовом API/сервисного слоя — быстро и надёжно. UI используем только для проверяемого сценария, а не для подготовки данных.

Антипаттерн — готовить данные через тот же UI, что тестируешь: медленно, хрупко и смешивает setup с проверкой.

Изоляция: каждый тест сам за себя

  • Каждый тест создаёт своё и не рассчитывает на данные «с прошлого раза».
  • Fresh fixture (свежее состояние на тест) предпочтительнее shared fixture (общего набора), который тесты молча мутируют.
  • Никаких цепочек «тест 1 создал — тест 5 использует»: тесты должны проходить в любом порядке и поодиночке.

Детерминизм: убрать случайность и время

  • Время: фиксируйте «сейчас» (clock/freeze), иначе тесты на «старше 18» или «истекло вчера» поплывут в полночь и в високосный год.
  • Рандом: Faker (и в Python) — отличный генератор, но с фиксированным seed. «Случайное имя» иногда выдаст пустую строку, апостроф или 60 символов — и тест мигает. Seeded-данные воспроизводимы.
  • Порядок: не полагайтесь на порядок выборки из БД без ORDER BY — он не гарантирован.

Уникальность: пережить параллельный прогон

При параллельных тестах главный враг — коллизии уникальных полей: два теста создают [email protected] → конфликт. Решение — встраивать уникальность в данные: user+{uuid}@test.com, имена с суффиксом-таймстампом/идентификатором воркера. Тогда тесты не дерутся за одни и те же значения.

Очистка: не оставлять мусор

  • Teardown: тест удаляет созданное в конце. Минус — при падении до teardown остаётся мусор.
  • Транзакционный откат: обернуть тест в транзакцию и откатить. Быстро и чисто, но не работает для многосервисных сценариев и закоммиченных данных.
  • Эфемерная БД: поднимать чистую базу на прогон (например, через Testcontainers — см. наш пост про Testcontainers). Максимальная изоляция, ценой ресурсов.
  • Уникальные данные (см. выше) частично снимают вопрос уборки: даже не убрав, вы не мешаете следующим прогонам.

Prod-like данные vs синтетика

  • Синтетика (фабрики/Faker) — основной источник: контролируемо, безопасно, детерминированно.
  • Дамп прода «как есть» в тесты — опасно: это PII/персональные данные (GDPR), а ещё привязывает тесты к случайному состоянию прода.
  • Если нужен реализм объёма/распределений — анонимизируйте/маскируйте дамп (имена, email, карты), а не тащите сырой прод.

Антипаттерны (красные флаги)

  • Хардкод id/«магических» записей со стенда (userId = 12345) — сломается при пересоздании стенда.
  • Цепочки зависимых тестов и общий мутируемый пользователь.
  • sleep(3) в ожидании, «пока данные появятся/проиндексируются» — ждите по факту готовности (поллинг условия), а не по часам.
  • Подготовка данных через UI вместо API.
  • «Случайные» данные без seed → мигающие тесты, которые винят код.
  • Тесты, которые проходят только в определённом порядке.

Чек-лист по тестовым данным (12 пунктов)

  1. Тест создаёт свои данные и не зависит от «прошлого прогона».
  2. Fresh fixture по умолчанию; общий мутируемый набор — только для неизменяемых справочников.
  3. Предусловия готовятся через API/фабрики, а не через тестируемый UI.
  4. Фабрики/билдеры вместо копипаста объектов (Object Mother / Test Data Builder).
  5. Время фиксировано (freeze clock) там, где логика зависит от дат.
  6. Рандом через Faker с фиксированным seed; никаких невоспроизводимых данных.
  7. Уникальные поля содержат uuid/worker-id, чтобы не коллизить в параллели.
  8. Очистка определена: teardown / транзакция / эфемерная БД.
  9. Тесты проходят в любом порядке и поодиночке (рандомизируйте порядок в CI).
  10. Никаких хардкод-id со стенда.
  11. Ожидание готовности данных — по условию, не sleep.
  12. Прод-данные в тестах только анонимизированные; сырой PII — нет.

Итог

Стабильность автотестов держится не только на хороших локаторах и auto-wait, но и на дисциплине с данными. Изоляция (своё на каждый тест), детерминизм (фиксированные время и seed) и уникальность (uuid в полях) убирают целый класс «мигающих» падений, которые иначе списывают на «ну это флак». Готовьте данные кодом, чистите за собой, не тащите сырой прод.

Источники