ТестированиеАвтоматизация тестированияИнженер по автоматизации тестирования

Как организовать генерацию случайных тестовых данных, чтобы найденный сбой можно было воспроизвести?

Как организовать генерацию случайных тестовых данных, чтобы найденный сбой можно было воспроизвести?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Нужно фиксировать начальное зерно генератора случайных данных и сохранять конкретный набор входных данных, на котором произошёл сбой. Для надёжного воспроизведения следует также записывать версию генератора и параметры генерации, поскольку одного зерна может быть недостаточно после изменения алгоритма или порядка вызовов.

Исторический контекст

Генерация случайных данных применяется в property-based testing, чтобы проверять свойства системы на множестве разнообразных входов, а не только на заранее выбранных примерах. Такой подход помогает находить комбинации значений и граничные случаи, которые разработчик не предусмотрел вручную.

Его исходная проблема — баланс между широким исследованием пространства входов и воспроизводимостью результата. Если каждый запуск использует другие данные, найденный дефект трудно подтвердить и исправить.

Постановка проблемы

Случайные данные полезны только тогда, когда падение можно превратить в стабильный регрессионный сценарий. Без фиксации параметров генерации один и тот же тест может пройти при повторном запуске, а команда потеряет исходный контекст ошибки.

Неверно полагаться только на текущий вывод генератора или на время запуска. Изменение версии библиотеки, порядка вызовов, числа итераций или параллельного выполнения может привести к созданию других входных данных даже при формально одинаковой конфигурации.

Подробное решение

При запуске теста задают или автоматически создают seed — начальное состояние генератора псевдослучайных чисел. При сбое seed выводят в отчёт, чтобы тест можно было повторить с тем же потоком псевдослучайных значений.

Надёжная схема обычно включает:

  • сохранение seed;
  • сохранение фактически сгенерированного входа;
  • запись версии генератора и параметров теста;
  • вывод номера итерации и, при необходимости, последовательности использованных значений;
  • сохранение минимального падающего примера после упрощения входа, или shrinking.

Сохранённый вход важнее одного seed: после изменения генератора он позволяет воспроизвести сам дефект без восстановления прежней последовательности случайных вызовов. После подтверждения проблемы вход следует добавить в постоянный набор регрессионных тестов, а генерацию оставить для дальнейшего поиска новых случаев.

Seed не устраняет всю недетерминированность. Если результат зависит от времени, внешнего сервиса, случайного порядка выполнения или общего состояния, эти источники также нужно контролировать; иначе одинаковый seed не гарантирует одинаковый результат.

Ситуация из практики

Тест проверяет обработку заказов и генерирует комбинации товаров, скидок и способов оплаты. В CI иногда обнаруживается нарушение бизнес-правила, но повторный запуск проходит, потому что новый запуск получает другой набор данных.

Рассматривались два варианта. Полностью отказаться от случайной генерации проще, но это уменьшает разнообразие проверок и возвращает зависимость от вручную выбранных примеров. Сохранять только seed дешевле по объёму отчётов, однако такой способ может перестать работать после обновления генератора или изменения последовательности вызовов.

Выбран подход с записью seed и фактического входного объекта, а после подтверждения дефекта — с добавлением входа в регрессионный набор. Генерация продолжает искать новые комбинации, а каждый найденный сбой получает воспроизводимый артефакт и может быть отдельно проверен после исправления.

Что кандидаты часто упускают

  1. Достаточно ли сохранять только seed?

Нет, это не всегда достаточно. Seed воспроизводит последовательность значений только при неизменных алгоритме генерации, порядке вызовов, параметрах и количестве итераций. Поэтому для надёжной диагностики следует сохранять и фактический вход, а seed использовать как дополнительную информацию для повторного запуска.

  1. Зачем уменьшать падающий случай, если его уже можно воспроизвести?

Большой случай может содержать десятки значений, из которых реально важны только два-три. Shrinking последовательно упрощает вход, сохраняя условие сбоя, чтобы получить минимальный пример. Такой пример легче понять, исправить и превратить в обычный регрессионный тест.

  1. Нужно ли после сбоя всегда фиксировать seed для всех будущих запусков?

Нет. Seed сбоя нужен для воспроизведения и расследования, но постоянное использование одного seed ограничит исследование новых входов. Обычно сохраняют падающий пример в регрессионном наборе, а генеративный тест продолжает работать с изменяющимися seed, записывая seed каждого неуспешного запуска.