Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Как добиться необходимого контроля, удобства и даже скорости при подготовке тестовых данных для микросервисов и тестов производительности? В каких случаях лучше не генерировать XML и JSON файлы с помощью конкатенации строк? Зачем анализировать статистику по SQL запросам?
Меня зовут Вячеслав Смирнов, и я ускоряю дистанционное банковское обслуживание юридических лиц, а еще поддерживаю чат QA — Load & Performance в Телеграм, где сообщество инженеров по тестированию производительности обсуждает тестирование нагрузки.
Статья получилась длинной, поэтому сегодня я расскажу про подготовку тестовых данных для тестирования производительности и про то, как с помощью SQL, Pandas и Java эти данные готовить. Поговорим про анализ метрик и логов с точки зрения данных и с использованием InfluxDB, Grafana и прочих инструментов. А ещё о том, как может выглядеть хороший отчет по системе, в которой много данных. В следующих частях перейду к генерации и анализу тестовых данных для нагрузки.
Видео моего выступления на эту тему можно посмотреть здесь, а по этой ссылке — презентация.
Для новых микросервисов неоткуда взять тестовые данные. У нас есть только микросервис и его пустая БД. В будущем сервис будет масштабироваться и БД вырастет за счет того, что пользователи будут с ней работать. И нам нужно сымитировать именно такую большую систему. Но можно попробовать сгенерировать данные через генераторы со стороны API и SQL.
Если мы будем генерировать через API, когда пользователь отправляет запросы, формирует тела этих запросов и дожидается ответов на них — будет надежно, но долго. Как правило, со стороны API отправляются JSON-файлы, по крайней мере, в тех банковских системах с которыми я имел дело.
Со стороны БД генерировать данные гораздо быстрее. Мы сразу вставляем готовую БД и можем, например, отправлять готовые CSV-файлы и целые таблицы. В этом случае сервер БД почти не напрягается, ведь мы сгенерировали данные, положили в CSV-таблицы и SQL запросом для импорта CSV-файла загрузили их прямо в таблицу:
COPY table_name [ ( column_name [, ...])]
FROM ‘filename’ [[WITH]
[DELIMITER [AS] ‘delimiter_character’]
[NULL [AS] ‘null_string’]
[CSV [HEADER]
[QUOTE [AS] ‘quote_character’]
[ESCAPE [AS] 'escape_character’]
[FORCE NOT NULL column_name [, ..]]]]
Но есть нюанс. Если вы генерируете, например, 500 ГБайт данных, то вам также придется сгенерировать 500 ГБайт CSV-файлов. Обычно свободных 500 ГБайт ни у кого нет. Поэтому я предпочитаю другой способ — использовать хранимые процедуры («хранимки»). В этом случае сервер БД и генерирует, и принимает данные. Нагрузка на него больше, но расход места на диске ниже.
Если нужны специфичные данные, которые нельзя сделать «хранимками», их можно сделать только на Java или Python, например, хэши паролей. Я их генерирую, сохраняю в CSV и загружаю готовыми. А самые сложные критичные данные, которых не надо много-много или если есть возможность подождать, я генерирую через API.
Клонировать или деперсонализировать невозможно или сложно
Если сервис не новый, то кажется, что корректные данные можно скопировать, то есть сделать клоны данных. В части систем это доступно, но в банковских системах нельзя получать доступ к данным клиентов. Потому что есть закон о персональных данных.
Требуется деперсонализация данных. Исходные данные меняются и перегоняются из одной базы данных в другую. Часто деперсонализация выполняется на уровне SQL-запросов. А данные на уровне SQL проверяются просто на целостность.
Если бы мы все данные создавали через интерфейс пользователя, то на уровне интерфейса мы бы всё заполнили, все данные были бы корректными, и мы бы в них точно не ошиблись. Если это делать через API, там будут выполняться бизнес-правила, бизнес-проверки самого сервера приложения, и мы тоже с высокой вероятностью не ошибемся. А вот если это делать только на уровне SQL, то ошибиться очень легко. Часто деперсонализация к этому и приводит:
Например, я видел деперсонализацию, где ФИО заменялось на md5 хэш, а название организации на случайные символы и цифры. Все это приводило к тому, что корректные данные становились некорректными: ФИО больше не проходило проверку ФИО, а номер ИНН не проходил проверку контрольной суммы.
На уровне интерфейса эти данные могли вообще не отображаться и вызывать ошибки. Например, мой коллега боролся с проблемой, что данные сгенерированы, но в интерфейсе их нет, потому что это более детальный уровень проверок. А в автотестах и нагрузочных тестах мы получаем постоянный процент ошибок:
Часть тестов, связанная с битыми тестовыми данными, вообще всегда провальная. Потому что данные клонировать сложно, а вот сделать качественную генерацию — намного проще.
Генерируем данные без Random
Еще одна особенность генерации данных для микросервисов в том, что все микросервисы взаимосвязаны. Они общаются друг с другом по разным протоколам, поэтому и данные должны быть взаимосвязаны. Рандом, который любят специалисты по генерации, тут запрещен.
Если у нас есть компания, то у нее должны быть клиенты, у которых должны быть документы. Если ID разойдутся, компания 1 будет пытаться получить документы компании 777 и ничего не получит, потому что данные не связаны. Поэтому для генерации согласованных и предсказуемых данных используется не Random, а числовой идентификатор объекта и математические преобразование.
Но особенности есть не только у генерации данных для микросервисов.
Подготовка данных для тестов производительности
Тесты для нагрузки должны быть атомарными. Рассмотрим атомарность на примере интерфейса входа в банк. Есть вход с верным паролем — это один тест-кейс.
Попытка входа с неверным паролем — это те же самые поля и тот же самый запрос, но нужны другие тестовые данные. Сброс пароля — это третий тест-кейс. При использовании этих кейсов лучше делать для них разделение тестовых данных. Например, по почтовому домену, чтобы логины заканчивались на разные домены (@ok.ru, @fail.ru, @pass.ru), чтобы они различались и не пересекались в разных действиях.
Для этого я делаю признак, который будут содержать логины и e-mail пользователей, и на его основе выделяю шаблоны и продумываю, какое количество будет для каждого из сценариев:
Информация о шаблонах и количестве ложится в основу методики тестирования, в профиль данных для нагрузки. Это важная часть методики тестирования.
Данные должны быть одноразовыми
Второй важный момент в нагрузке, как и в автоматизации — данные должны быть одноразовыми. Например, у вас есть вход по логину и паролю. Можно дважды взять одни и те же тестовые данные и попробовать войти с неверным паролем, но дважды сбросить пароль нельзя.
Если вы его уже сбросили в системе — то он сброшен, уже изменился и произошла модификация. Дважды сменить пароль нельзя потому, что я меняю пароль 1 на пароль 2, и повторно сменить пароль 1 на пароль 2 уже невозможно. Теперь уже новый пароль — пароль 2.
Нельзя дважды войти в систему первый раз, поэтому данные должны быть одноразовыми, чтобы не было ошибок. Иначе не получится протестировать такие сценарии:
Если начать менять изменённый пароль, то будет ошибка.
Если повторно зайти пользователем, которого ждут шаги первого входа, то сценарий не выполнится.
Данные не должны пересекаться между агентами
Часто в нагрузке применяется распределенный запуск нагрузки, когда есть микросервис, мы на три Linux’овых агента затянули код и с этих трех станций подается нагрузка на одну систему. Чтобы три станции работали без конфликтов, блокировок и не отнимали друг у друга идентификаторы тестовых данных, нужно обеспечить уникальность данных для каждого агента.
Один из распространенных подходов, но не скажу, что самый лучший — использовать еще одну БД, в которую все тесты будут ходить и брать оттуда id тестовых данных или использовать HTTP API для получения данных. Распространённые системы: Virtual Table Server (VTS) из HP LoadRunner и Simple Table Server из Apache.JMeter, которые работают по протоколу HTTP. Кто-то пользуется AMQP или Redis. Я рекомендую разные CSV-файлы для разных агентов.
Если у меня три агента, я генерирую и сохраняю три CSV файла. Они read only, там нет блокировок и работа с ними максимально быстрая. Я добиваюсь того, что первый агент использует первый CSV-файл, второй — второй и третий — третий или задаю это в property в конфигурационном файле или другим способом. Например, делаю чтобы имя файла соответствовало имени агента. Это возможно, если ваши агенты постоянные, а не создаются в Kubernetes на каждый тест. Чаще всего нагрузочные агенты делают именно постоянными.
Проводить тесты часто, данные готовить быстро
Ещё одна особенность тестов производительности в быстрой подготовке тестовых данных. Например, мы используем 100 тысяч объектов и миллион объектов в БД для CSV файлов, но за час тестирования изменилось только 10 тысяч объектов — 1% от объектов в БД. Не будем же мы после каждого изменения 1% перегенерировать всю базу данных или восстанавливать ее.
Есть разные подходы:
Сгенерировать данные — самый долгий подход. Я условно указал, что это неделя работы, но бывает по-разному. А вот сделать скрипт, который обновит только измененные записи — это самый быстрый подход.
Если у вас есть возможность написать скрипт генерации или до обновления, восстановления тестовых данных, то напишите его. Он будет экономить вам время, а вы только раз в месяц, например, будете восстанавливать данные из бэкапа. Это можно делать из полного бэкапа, достаточно долгий процесс, или восстанавливать по журналу транзакций. Это более быстрый процесс, хотя из-за другой структуры, бэкап с индексами, ключами и журналом транзакций физически занимает больше места, он восстанавливается гораздо быстрее.
А самый быстрый метод — обновлять измененные записи потому, что журнал транзакций вырастет не намного, вы измените лишь 1% данных. Я стремлюсь использовать такой подход, и вам тоже советую — он самый простой.
В следующей части я расскажу про генерацию данных с PostgreSQL, Python и сериализацией классов.
Конференция об автоматизации тестирования TestDriven Conf 2022 пройдёт в Москве, 28-29 апреля 2022 года. Если вы хотите выступить на конференции, то до окончания приема заявок осталось 8 дней.
Кроме хардкора об автоматизации и разработки в тестировании, будут и вещи, полезные в обычной работе (например, софт-скилы и юридические аспекты). Принятые доклады можно посмотреть здесь, а купить билет — здесь.