В предыдущей статье я рассказал о подготовке данных для тестирования, что данные лучше генерировать, а не клонировать. Теперь стоит подробно разобрать, как их генерировать. Есть несколько подходов к генерации данных: c SQL, Python, сериализацией. У всех из них есть свои плюсы, минусы и особенности, которые стоит учитывать.
![](https://habrastorage.org/getpro/habr/upload_files/3f5/ba1/2c2/3f5ba12c2ba6dd94b80f447f904fd56e.png)
Генерация данных с PostgreSQL
Чаще всего я работаю с Postgres. Я пришел к структуре генератора тестовых данных, которой хочу с вами поделиться. Она представляет из себя набор хранимых процедур, где есть корневая хранимая процедура, в ней есть параметры. Она вызывает служебные хранимые процедуры, которые вызывают служебные хранимые процедуры для сценариев, которые вызывают уже супер служебные процедуры, которые выполняют атомарные действия: получи пароль, получи имя, заполни какую-то запись.
Сверху это выглядит как супер-параметризированная структура, где я говорю, что мне нужно по таким-то шаблонам сгенерировать столько-то тысяч записей и запускаю цикл.
![Функции-обертки с параметрами генерации данных для сценариев, например, функция load_fill_database_change_password для сценария change_password Функции-обертки с параметрами генерации данных для сценариев, например, функция load_fill_database_change_password для сценария change_password](https://habrastorage.org/getpro/habr/upload_files/3f5/20a/e34/3f520ae3482faf446f30217f244da7da.jpeg)
Эти количества и шаблоны, конечно, беру из методики, которую заранее подготовили. Параметры передаются в функцию, которая уже на основе этих шаблонов готовит данные для конкретного сценария. В ней цикл от 0 до 10000, чтобы делать INSERT INTO, INSERT INTO и различные вычисления.
![Параметризируемая функция генерации данных с циклами генерации данных Параметризируемая функция генерации данных с циклами генерации данных](https://habrastorage.org/getpro/habr/upload_files/37a/279/b81/37a279b81fc4c80a1f365b8c8e64f649.jpeg)
Подходы для генерации значений
В Postgres SQL можно добиться того, что все данные будут согласованы, идентификаторы высчитаны, а вычисления реализованы на языке SQL или использованы дополнительные расширения.
Но такой подход реализуем:
![Цикл генерации значений, где каждое значение зависит от счетчика цикла Цикл генерации значений, где каждое значение зависит от счетчика цикла](https://habrastorage.org/getpro/habr/upload_files/b70/2d7/27e/b702d727e0fe61c1a03e633752f70750.png)
Мы бежим в цикле и на основе индекса вычисляем, например, имя и фамилию пользователя 1. У него будет индекс №1. Для индекса №2 используются аналогичные вычисления. Конечно, данные получатся не такими случайными, как в реальной жизни, но они будут корректными.
Я знаю такие подходы:
Число из интервала
Взять остаток от деления на N: это даст число в интервале от 0 до N-1.
timeDiffMinutes = i % 80000
Логин пользователя
Или использовать функцию, которая в SQL для Postgres называется format. Там есть шаблонные строки и параметры.
nick_name = format( 'user%sname', i::text )
Пример результата:
user123name
IP-адрес
Можно объединить, например, format и остаток от деления для того, чтобы вычислять IP-адреса в журнал входов в систему или таблицу аудита.
Для IPv4
format( '106.%s.%s.%s', (((ip_index/250)/250)%250)::text, --- 0..249 ((ip_index/250)%250)::text, --- 0..249 (1+ip_index%250)::text --- 1..250 )
Пример результата:
106.0.1.2
А всего 15 625 000 значений
GUID через md5
Часто идентификаторами или ключами в таблицах являются GUID’ы. Я придумал способ, чтобы вычислить GUID для теста. Например, если нужен GUID в таблицу users
для пользователя №175000 для сценария login, то можно взять
-
число
175000
, превратить его в строку фиксированной длины через lpad -
конкатенировать с именем таблицы
'users'
-
и конкатенировать с названием теста
'login'
Взять от результата md5 хэш и привести его к типу uuid. Так мы получим уникальный GUID:
guid = md5('users' || test_name || lpad(i::text, 12, '0') )::uuid
Пример результата:
faf154aa-54d1-0b07-3c8e-fe8921f96c45
GUID через конкатенацию строк
Я знаю, что на специально подготовленных данных в md5 бывают проблемы — коллизии. Но при использовании имени таблицы, имени теста и номера строки в тесте, у меня за всю практику ни разу коллизий не было. Поэтому я рекомендую вам md5 для генерации предсказуемых, но при этом уникальных GUID. Если сомневаетесь, можете сформировать md5 с префиксами: идентификатор теста замените на другую подстроку и используйте номер вашей записи. Получится GUID для записи 1, например, префикс + 000000000001:
guid = ('22-33-44-55' || 'aa-bb-cc-dd-ee-ff' || lpad(i::text, 12, '0'))::uuid
Пример результата:
22334455-aabb-ccdd-eeff-000000000001
Выбор констант из массива
Можно выбирать данные из массивов:
array[ 1 + mod( abs("userIndex"), arrayLenght ) ]
Предел на размер массива данных — 1 ГБайт, тестировал на массиве в 10 МБайт. Использую 64 КБайт (примерный размер сценария ниже — 64 КБайт):
-- Function: public.load_get_name(integer) -- DROP FUNCTION public.load_get_name(integer); CREATE OR REPLACE FUNCTION public.load_get_name("userIndex" integer DEFAULT 0) RETURNS text AS $BODY$ DECLARE namearray text[] := array[ 'Авдей', 'Авдий', 'Авенир', 'Аверкий', 'Авксентий', 'Агафон', --- '…', 'Фёдор', 'Харитон', 'Христофор', 'Эдуард', 'Эраст', 'Юлиан', 'Юлий', 'Юрий', 'Юстин', 'Яков', 'Якун', 'Ярослав' ]; nameArrayLenght integer := array_lenght(nameArray, 1); BEGIN RETURN nameArray[ 1 + mod(abs("userIndex"), nameArrayLenght) ]; END $BODY$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION public.load_get_name(integer) OWNER TO postgres;
Выбор констант из справочной таблицы
Или выбирать данные из целых таблиц с помощью SELECT, которые вы загрузили из CSV файла.
CREATE FUNCTION public.load_get_password("userLogin") RETURNS text AS $BODY$ DECLARE password varchar(255); BEGIN password = (select password_hash from test_passwords where user_name="userLogin" limit 1); RETURN password; END $BODY$ LANGUAGE plpgsql VOLATILE;
Я рассказал про подходы, но я бы не был нагрузочником, если бы не сказал, как эти подходы ускорить.
Ускорение вставки значений
Чтобы ускорить вставку значений не только за счет использования хранимых процедур, а еще за счет оптимального процесса, я рекомендую до генерирования данных сделать еще 4 предварительных шага:
1) Сохранить схему БД в виде файла — вдруг что-то поломается;
2) Отключить индексы запросом для Postgres:
UPDATE pg_index SET indisready=false WHERE indexrelid = ( SELECT oid FROM pg_class WHERE relname in ( 'таблица1' ,'таблица2' ) );
Здесь мы говорим, что нужные индексы у нас теперь (indisready=false) отключены.
3) Отключить триггеры, если они есть:
ALTER TABLE public.таблица1 DISABLE TRIGGER ALL; ALTER TABLE public.таблица2 DISABLE TRIGGER ALL;
4) Удалить ключи:
ALTER TABLE public.таблица1 DROP CONSTRAINT таблица1_pkey; ALTER TABLE public.таблица2 DROP CONSTRAINT таблица2_pkey;
Ключи являются индексами, но отключить их нельзя. Поэтому если вы хотите ускорить тестирование и отказаться от ключей, их можно только удалить. Для этого мы и сохраняли схему БД на первом шаге, чтобы потом проверить, что у нас все хорошо.
5) Сгенерировать данные «хранимками»:
SELECT load_fill_database_test1(1, 1000000); SELECT load_fill_database_test2(1000001, 2000000);
Далее нужно выполнить шаги обратные шагам 4, 3, 2.
5) Создать ключи (ADD CONSTRAINT):
ALTER TABLE public.таблица1 ADD CONSTRAINT PRIMARY KEY(id); ALTER TABLE public.таблица2 ADD CONSTRAINT PRIMARY KEY(id);
6) Включить триггеры, если мы их отключали:
ALTER TABLE public.таблица1 ENABLE TRIGGER ALL; ALTER TABLE public.таблица2 ENABLE TRIGGER ALL;
7) Включить индексы:
UPDATE pg_index SET indisready=true WHERE indexrelid=( SELECT oid FROM pg_class WHERE relname in ('таблица1', 'таблица2') );
Если отключить индексы, а потом вставить данные и включить индексы, индексы автоматически не обновятся. Нужно выполнить переиндексацию.
8) Переиндексировать:
REINDEX public.таблица1; REINDEX public.таблица2;
Теперь Postgres будет знать о всех новых записях, индексы перестроятся и можно сравнить сохраненную схему, которая была на шаге 1, и схему, которая получилась после того, как мы выполнили все шаги.
9) Сравнить схему до и после.
Но данные можно генерировать не только с Postgres. Иногда нужно генерировать сложные данные, например, списки сотрудников, которые получают зарплату, в виде документа Excel. Тогда мне на помощь приходит Python и проект под названием Pandas.
Генерация данных с Python
Я работал с Pandas и проектом, который раньше назывался Elizabeth, а сейчас Mimesis:
Его использовал, чтобы генерировать большое количество псевдоперсональных данных для тестирования зарплатных проектов. Вот пример алгоритма генерации сотрудниц.
![В Elizabeth создается случайная персона и для нее можно получить фамилию и имя В Elizabeth создается случайная персона и для нее можно получить фамилию и имя](https://habrastorage.org/getpro/habr/upload_files/3bb/538/0d6/3bb5380d6f9b77a5698bee7f228f5aa6.jpeg)
В цикле формируется 600000 строк, и чтобы Mimesis сгенерировал русские имена и фамилии, мы в строке 16 задаем персону для русского языка. В классе person у нас появляются все псевдослучайные персональные данные. Если мы возьмем, например, поле surname или name и вставим их в наши поля Pandas, то получим женскую фамилию и женское имя. Несложным алгоритмом можно получить отчество.
![Подбираем отца перебором, пока имя отца не будет заканчиваться на к, н, г, з, ф, в, п, р, л, д, ч, с, м, т или б. Для Павла и Льва - особые условия формирования отчества Подбираем отца перебором, пока имя отца не будет заканчиваться на к, н, г, з, ф, в, п, р, л, д, ч, с, м, т или б. Для Павла и Льва - особые условия формирования отчества](https://habrastorage.org/getpro/habr/upload_files/08c/374/799/08c374799b5bc7e24e2a26fa42f0be20.jpeg)
Для этого нужно сгенерировать уже мужское имя и посмотреть на какую букву алфавита оно заканчивается. Если на простую букву, например, имя Кирилл заканчивается на букву «Л», то чтобы сгенерировать женское отчество от имени Кирилл, нужно добавить «овна» — Кирилловна. Есть несколько исключений, для которых надо добавлять дополнительные правила. Например, имя Павел тоже заканчивается на простую букву «Л», но Павеловна — некорректное отчество.
Аналогично генерируются мужские отчества — Иван — «ович».
На Python можно генерировать другие данные, выполнять транслитерацию и генерировать номера.
![Генерация имени для пластиковой карты из 23 символов Генерация имени для пластиковой карты из 23 символов](https://habrastorage.org/getpro/habr/upload_files/902/a3a/971/902a3a97192ae28e4698a6153514fc15.jpeg)
Ещё Pandas предоставляет удобный интерфейс для того, чтобы удалить дубликаты в одну строку.
df = pd.DataFrame(rowList) df = pd.drop_duplicates(keep="first")
Или для того, чтобы массово обновить все поля всех строк, например, сказать, что в этом датасете все родились 25 ноября:
![Массовое добавление полей Массовое добавление полей](https://habrastorage.org/getpro/habr/upload_files/516/9a7/836/5169a783687992c65610b75096305e5e.jpeg)
Я использовал Pandas для того, чтобы не разбираться с форматами Excel, а сохранить готовые данные в CSV или XLS файлы.
![Выгрузка в csv Выгрузка в csv](https://habrastorage.org/getpro/habr/upload_files/84c/32c/6b8/84c32c6b8482485112ed030e042bbd40.jpeg)
Генерация данных с сериализацией классов
Если вы генерируете данные со стороны API, и эти данные представляют структуры данных сервиса, то переиспользуйте их. Например, вам нужно отправить много JSON файлов в вашу систему. Часто тестировщики соединяют строки и вставляют параметры, чтобы в результате получился валидный JSON. Это плохой путь.
Хороший путь — это пойти к разработчикам или скачать исходники, взять Data Transfer Object, которые уже написаны, версионируются и поддерживаются вместе с вашим сервисом, где есть специальные методы, чтобы только имя заполнить, а все остальное сгенерировать, проверить и рассчитать.
![](https://habrastorage.org/getpro/habr/upload_files/3dd/67b/251/3dd67b25171154d78ed8eb9750aa1ac8.jpeg)
И самое главное — Data Transfer Object в одну строку превращаются в XML-файл или JSON-файл. Вам не нужно для этого делать конкатенацию. Возможно, это будет чуть дольше, но вы сэкономите много времени на отладке.
Промежуточные итоги
-
генерация данных на SQL — наиболее производительный способ генерации, который работает максимально быстро, если предварительно настроить базу данных на быструю вставку
-
генерацию данных в формате Excel/CSV удобно делать с Pandas, а для псевдоперсональных данных удобно использовать Mimesis из python
-
для формирования XML/JSON, при генерации данных через API, сериализация объектов системы дает более стабильный результат, чем формирование XML/JSON через конкатенацию строк
В следующей части я расскажу об анализе данных и логировании запросов PostgreSQL.
Конференция об автоматизации тестирования TestDriven Conf 2022 пройдёт в Москве, 28-29 апреля 2022 года. Если вы хотите выступить на конференции, то до окончания приема заявок осталось 8 дней.
Кроме хардкора об автоматизации и разработки в тестировании, будут и вещи, полезные в обычной работе. Принятые доклады можно посмотреть здесь, а купить билет — здесь.
ссылка на оригинал статьи https://habr.com/ru/articles/589543/
Добавить комментарий