Использование случайностей в функциональном тестировании
Для кого эта статья?
Инженеры по автоматизации и разработчики тестов — вам точно будет интересно.
Обычные разработчики, если вы заинтересованы в качестве продукта, а не считаете тесты "расплатой за грехи" или "прихотью менеджмента" — тоже.
А кого, возможно, не заинтересует
Если у вас постоянно есть падающие тесты, и никто не разбирается в причинах, а просто смотрят на процент "зелёных" и "красных", — эта статья может показаться бесполезной.
Откуда вообще эта идея?
Возможно, вы сталкивались с ситуацией, когда один и тот же тест гоняется с разными входными данными. Типичный data-driven подход: параметризуем, и всё работает.
На первый взгляд — всё логично. Но на практике:
увеличивается время прогона (особенно для UI-тестов);
растёт вероятность нестабильности (flaky-тесты);
тесты зачастую дублируют поведение друг друга.
Наглядный пример
Допустим, у нас есть UI-тест, проверяющий переходы по меню на сайте. Стартовая страница содержит меню для перехода на страницы A, B, C, D.
Сценарий теста:
Открываем стартовую страницу.
Выбираем пункт в меню.
Проверяем, что оказались на нужной странице.
Что делает большинство:
Пишут четыре теста: переход на A, на B, на C и на D.
Или параметризуют тест: гоняют один и тот же сценарий с разными входными.
Но по сути, мы проверяем одну и ту же функциональность перехода по меню. Зачем гонять одни и те же шаги с разными данными?
Проблема
Если тест проверяет функциональность, то логично оставить один вариант— переход на страницу A. Экономите ресурсы, всё стабильно и функциональность проверяется. Но однажды приходит тимлид с вопросом:
Почему тесты не поймали баг с переходом на страницу D?
В этом случае экономия не оправдалась
Что делать?
Тест — это тоже код. Он может быть не стабильным. И чем чаще он запускается, тем быстрее мы понимаем, стабилен он или нет.
А теперь возвращаемся к примеру с меню:
Что, если каждый раз случайным образом выбирать один из пунктов (A, B, C, D)?
Экономим ресурсы: запускается один тест вместо четырёх.
С течением времени мы случайным образом "покроем" все пункты.
Чем чаще прогоняются тесты, тем быстрее мы обнаружим баг (например, если страница C не открывается).
Это не универсальное решение, но в ряде случаев — вполне разумный компромисс между экономией и эффективностью.
Это не ноу-хау
Такой подход давно известен — он называется property-based testing.
Как пример - фреймворк Hypothesis, который позволяет генерировать данные автоматически и находить пограничные случаи, о которых вы даже не думали.
Что важно помнить
Использование случайности не должно усложнять тест. Логика должна быть понятна и читаема.
Если тест упал, он должен сообщить, что именно пошло не так, даже если данные были сгенерированы случайно.
Рандомизация — всего лишь один из способов сделать тесты более эффективными, но это не универсальное решение
Иногда стоит логировать/фиксировать сгенерированные данные, особенно при падении теста — чтобы потом воспроизвести баг
Итог
Рандомизация в тестировании — мощный инструмент:
Но применять её стоит с умом: понимать, зачем, где, и в каком объёме.
А как у вас это устроено? Используете ли вы property-based подход в тестах? Или всё ещё параметризуете всё подряд? Буду рад услышать ваши мнения, кейсы и даже критику.