Привет, Хабр!
Если ваш проект вырос, в нем бэкенд с фронтендом, различные точки входа API, интеграции с внешними системами, сложные алгоритмы, проверки введенных данных пользователем на валидность, диаграммы бизнес-процессов имеют тысячи ветвей, то скорее всего регрессионное тестирование занимает кучу времени, и проводить его вручную уже невыгодно. Проще эту работу поручить машине и тестировать продукт автоматически. Первый вопрос, который возникает — «Как генерировать данные?», а конкретнее, как генерировать то, что может ввести пользователь. Этот вопрос мы и разберем в данной статье.
Обычно введенные строки сравнивают с некими шаблонами, среди которых наиболее распространенный– RegExr (регулярное выражение). Самый логичный способ — тестировать шаблон шаблоном.
Что такое и из чего состоит RegExr:
RegExr — шаблон, в котором представлена совокупность правил для конкретных позиций в строке.
Для единичных символов понятно, на этой позиции может быть только он. А в квадратных скобках указаны возможные варианты символов, например [1-9#] на этой позиции возможны только символы — 123456789#, а для правила [^@\.] возможны любые символы кроме @ и . (напомню, что символ ^ после открывающей скобки означает «кроме»)
Квантификатор даёт нам информацию о том, на сколько позиций распространяется символ или символьная группа перед ним.
Квантификатор | Использование |
{n} | Ровно n повторений |
{m,n} | От m до n повторений |
{m,} | Не менее m повторений |
{,n} | Не более n повторений |
? | Ноль или одно повторение То же, что и {0,1} |
* | Ноль или более повторений То же, что и {0,} |
+ | Одно или более повторений То же, что и {1,} |
Все как в алгебре, скобки группируют и в основном работают с квантификаторами. В примере выражение в скобках может повторяться один и более раз:
Логика “или” позволяет сделать наше выражение более эластичным. В примере строка может состоять либо из 6 цифр, либо из 10. Выражений разделенных элементом | может быть сколько угодно, но на этом месте может быть только одно из них.
Проблематика:
Например, поле email не принимает случайный набор символов, естественно там присутствует проверка через регулярное выражение, и в самом простом виде это [^@]+@([^@\.]+\.)+[^@\.]+. Соответственно, нам нужно либо захардкодить значение на каждое поле, либо при каждом тесте генерировать строку, которая подойдет под это выражение.
Чем обернется хардкод:
1. Вы не сможете поддерживать свои тесты, так как ваш продукт меняется, и искать часами, где именно прошлый разработчик указал данные для этого поля долго и невыгодно.
2. Возникает риск ошибки, ведь по основному принципу тестирования мы тестируем черный ящик и не знаем какое там регулярное выражение, так как программист мог спокойно указать [^@]+@[^@\.]+\.[^@\.]+, и misha@mail.ru пройдет, а diana@inform.bigdata.ru нет.
3. Банально под ваши тесты могут подогнать регулярное выражение :)
Соответственно, правильно будет каждый раз генерировать новую случайную строку. Рекомендую писать свое регулярное выражение для генерации, основываясь на ТЗ, а не таскать их из кода. Для того, чтобы сгенерировать случайную строку, нужно решить две основные проблемы:
1. Какие рамки поставить для квантификаторов +, {m,}, *. Ведь бесконечное количество символов мы сгенерировать не можем, а строка из тысячи символов только нагрузит интерфейс и особо никак не повлияет на тестирование. Тут нужно найти баланс и случайно не написать стресс-тест.
2. Откуда брать символы для генерации такого выражения — [^@\.]? Из таблицы ASCI? Но там довольно много специальных символов, и в итоге мы получим непонятную кашу.
Ответив на эти вопросы, мы сможем реализовать генерацию. Первый вопрос решается выбором верхней планки длины строки или квантификатора, например 50 символов. А второй — собственной библиотекой символов для проекта.
Задача:
Для генерации строки из выражения нужно разложить его на такие части, которые будет принимать конечный автомат. То есть нужно привести к такому виду, что некая функция будет принимать его части на вход (множество символов B), выбирать такой символ a, который принадлежит этому множеству и выдавать этот символ.
Метод генерации строки из регулярного выражения
Приведу свою блок-схему алгоритма генерации включающую в себя 5 основных этапов:
1 Этап — декомпозиция основного выражения на подгруппы:
2 Этап — выбор между возможными вариантами:
Для оптимизации времени выполнения алгоритма, этот этап выполняется сразу после декомпозиции и позволяет не тратить времени на генерацию ненужных символов.
3 Этап — обработка символьных групп:
На этапе происходит замена шаблонов и формирование множеств для конечных автоматов.
4 Этап — обработка квантификаторов:
На этом этапе нам требуется выбрать конкретные значения для квантификаторов. Тут мы сталкиваемся с проблемой описанной ранее, квантификаторы +, *, {n,} представляют диапазоны чисел от n до бесконечности, с такими диапазонами мы работать не можем, поэтому требуется ограничение, например от n до n + 50. Потом для диапазонов нам нужно выбрать конкретное значение, оно и будет означать количество запусков конечных автоматов. Далее мы «разворачиваем» выражения в круглых скобках в зависимости от значения их квантификатора.
Отмечу, что мы должны рекурсивно запускать алгоритм для каждой группы столько раз, сколько указано в её квантификаторе.
Приведу пример работы квантификатора, который должен запускать именно то количество конечных автоматов, которое указано для него, а не «умножать» результат работы.
5 Этап — Запуск конечных автоматов.
После всех этих этапов мы получаем общность конечных автоматов, которые и требуется запустить:
Зачем изобретать велосипед?
Увы, но нет универсальных решений, и под свои задачи нужно либо модифицировать, либо писать что-то самостоятельно. Например, на нашем проекте для автоматического тестирования API был выбран язык Python. Он простой, удобный, и обучение специалиста, который сможет поддерживать фреймворк — дело буквально месяца-двух. У нас несколько требований к библиотеке генерации случайных строк:
1. Быстрая;
2. Удобная;
3. Должна поддерживать основную лексику регулярных выражений;
4. В начале и конце сгенерированной строки не должно быть пробелов
В своих поисках мы нашли 4 библиотеки: xeger, strgen, exrex, rstr.
Strgen – самая быстрая, практически идеальна, но не поддерживает многие квантификаторы.
Xeger — реализована не самая удобная работа с библиотеками символов и первоначальными настройками.
Rstr — Не самая стабильная библиотека, в некоторых моментах работает в 5 раз медленнее других.
Exrex — генерирует 1000 случайных строк в среднем за 2 секунды, не критично, но можно лучше.
Хотелось бы получить универсальную, быструю библиотеку, подходящую под наши требования. Вот так и появилась на свет библиотека re_generate (pypi.org/project/re-generate). Получилось даже лучше, чем мы думали, уже до оптимизации алгоритм выдает хорошие показатели в сравнительных бенчмарках:
Итог:
Если вы не нашли уже готового решения для вашего проекта, рекомендуем реализовать этот алгоритм, таким образом вы получите практику и прекрасный инструмент для работы. Забирайте в свои проекты, пользуйтесь, реализуйте. Спасибо за внимание!