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