Как говорят иные отважные люди, «от dev до prod — всего один шаг». Люди опытные добавляют, что шаг этот называется «тестирование», причём самое разнообразное, и нам просто нет смысла им не верить.
Нагрузка имеет значение: водитель этого грузовика умудрился обрушить мост весом своего ТС, счёт за восстановление составил примерно 16m. К счастью, тестирование ПО обходится дешевле!
Конечно, говоря о тестировании, нужно понять, с чем и за что мы боремся. Мы сознательно ограничили себя и решили сегодня поговорить исключительно про нагрузочное тестирование и тестирование производительности: темы, полярно удалённые друг от друга, крайне интересны в самом практическом выражении. Рассмотрим инструменты для того и другого, не привязываясь к какому-то конкретному стеку технологий, так что не удивляйтесь соседству Яндекс.Танк и BenchmarkDotNet!
Нагрузочное тестирование
Представим, что мы с вами написали некий сервис — теперь нужно понять, какую нагрузку он выдержит. Старая грустная шутка разработки качественного ПО гласит, что лучше иметь софт, который работает гарантированно плохо, чем софт, работающий хоть и хорошо, но негарантированно хорошо: в первом случае мы хотя бы будем знать, на что твёрдо можем рассчитывать. Далее, если наш сервис умеет масштабироваться тем или иным образом, то нужно понять, насколько с ростом нагрузки масштабирование оказывается полезным и выполняет ли оно возложенные на него проектом задачи.
Что ж, берём и направляем на наше детище нагрузку, притом внимательно наблюдая за результатом: нас, очевидно, интересует ситуация, когда сервис либо станет отвечать на запросы с неприемлемой задержкой, либо будет возвращать неверные данные, либо вовсе перестанет подавать признаки жизни для всех запросов или лишь для их части.
Давайте представим, что мы с вами написали некоторый сервис — для определённости скажем, что веб-сервис, но по сути это не столь важно. Чтобы убедиться, на что мы с ним можем рассчитывать, мы начинаем «обстреливать» его запросами, наблюдая за поведением как самого сервиса, так и за нагрузкой на серверах, где он крутится. Хорошо, если заранее понятно, какие запросы нам нужно отправлять сервису (в этом случае мы можем подготовить массив запросов заранее, а после отправить его в наше приложение одним махом). Если же второй запрос зависит от результатов первого (простой пример — сначала происходит авторизация пользователя, и в следующие обращения к сервису включается информация об ID сессии), то генератор нагрузки должен быть способен генерировать тестовые запросы крайне быстро, в реальном времени.
С учётом обстоятельств и нашего знания об объекте тестирования выбираем инструмент(ы):
JMeter
Да, старый добрый JMeter. Он вот уже без малого двадцать лет (!) является частым выбором для многих вариантов и типов нагрузочного тестирования: удобный GUI, независимость от платформы (спасибо Java!), поддержка многопоточности, расширяемость, отличные возможности по созданию отчётов, поддержка многих протоколов для запросов. Благодаря модульной архитектуре JMeter можно расширить в нужную пользователю сторону, реализуя даже весьма экзотические сценарии тестирования — причем, если ни один из написанных сообществом за прошедшее время плагинов нас не устроит, можно взять API и написать собственный. При необходимости с JMeter можно выстроить, хоть и ограниченное, но распределённое тестирование, когда нагрузка будет создаваться сразу несколькими машинами.
Одной из удобных функций JMeter является работа в режиме прокси: указываем в настройках браузера в качестве прокси «127.0.0.1:8080» и посещаем браузером нужные нам страницы нужного сайта, тем временем JMeter сохраняет все наши действия и все сопутствующие запросы в виде скрипта, который позже можно будет отредактировать, как нужно — это делает процесс создания HTTP-тестов заметно проще.
Кстати, последняя версия (3.2), вышедшая в апреле этого года, научилась отдавать результаты тестирования в InfluxDB при помощи асинхронных HTTP-запросов. Правда, начиная как раз с версии 3.2, JMeter стал требовать только Java 8, но это, наверное, не самая высокая цена за прогресс.
Хранение тестовых сценариев у JMeter реализовано в XML-файлах, что, как оказалось, создаёт массу проблем: их совсем неудобно писать руками (читай — для создания текста необходим GUI), как неудобна и работа с такими файлами в системах управления версиями (особенно в момент, когда нужно сделать diff). Конкурирующие на поле нагрузочного тестирования продукты, такие, как Яндекс.Танк или Taurus, научились самостоятельно и на лету формировать файлы с тестами и передавать их в JMeter на исполнение, таким образом пользуясь мощью и опытом JMeter, но давая возможность пользователям создавать тесты в виде более читаемых и легче хранимых в CVS тестовых скриптов.
LoadRunner
Ещё один давно существующий на рынке и в определенных кругах очень известный продукт, большему распространению которого помешала принятая компанией-производителем политика лицензирования (кстати, сегодня, после слияния подразделения ПО компании Hewlett Packard Enterprise с Micro Focus International, привычное название HPE LoadRunner сменилось на Micro Focus LoadRunner). Интерес представляет логика создания теста, где несколько (наверное, правильно сказать — «много») виртуальных пользователей параллельно что-то делают с тестируемым приложением. Это даёт возможность не только оценить способность приложения обработать поток одновременных запросов, но и понять, как влияет работа одних пользователей, активно что-то делающих с сервисом, на работу других. При этом речь идёт о широком выборе протоколов взаимодействия с тестируемым приложением.
HP в своё время создала очень хороший набор инструментов автоматизации функционального и нагрузочного тестирования, которые, при необходимости, интегрируются в процесс разработки ПО, и LoadRunner умеет интегрироваться с ними (в частности, с HP Quality Center, HP QuickTest Professional).
Некоторое время назад производитель решил повернуться лицом к тем, кто не готов сразу платить за лицензию, и поставляет LoadRunner с бесплатной лицензией (где вписан лимит на 50 виртуальных пользователей, и запрещена небольшая часть всего набора поддерживаемых протоколов), а деньги берутся за дальнейшее расширение возможностей. Сложно сказать, насколько это будет способствовать повышению интереса к этому, без сомнения, занимательному инструменту, при наличии у него столь сильных конкурентов.
Gatling
Весьма мощный и серьёзный инструмент (не зря названный в честь скорострельного пулемета) — в первую очередь, по причине производительности и широты поддержки протоколов «из коробки». Например, там, где нагрузочное тестирование с JMeter будет медленным и мучительным (увы, плагин поддержки работы с веб-сокетами не особо быстр, что идейно конфликтует со скоростью работы самих веб-сокетов), Galting почти наверняка создаст нужную нагрузку без особых сложностей.
Следует учесть, что, в отличие от JMeter, Gatling не использует GUI и вообще считается средством, ориентированным на опытную, «грамотную» аудиторию, способную создать тестовый скрипт в виде текстового файла.
Есть у Gatling и минусы, за которые его критикуют. Во-первых, документация могла бы быть и получше, во-вторых, для работы с ним неплохо знать Scala: и сам Gatling, как инструмент тестирования, и тестовые сценарии пишутся именно на этом языке. В-третьих, разработчики «иногда» в прошлом кардинально меняли API, в результате можно было обнаружить, что тесты, написанные полугодом ранее, «не идут» на новой версии, либо требуют доработки/миграции. У Gatling также отсутствует возможность делать распределённое тестирование, что ограничивает возможные области применения.
Яндекс.Танк
Если коротко, Yandex Tank — это враппер над несколькими утилитами нагрузочного тестирования (включая JMeter), предоставляющий унифицированный интерфейс для их конфигурации, запуска и построения отчётов вне зависимости от того, какая утилита используется «под капотом».
Он умеет следить за основными метриками тестируемого приложения (процессор, память, своп и пр.), за ресурсами системы (свободная память/место на диске), может остановить тест на основе разных понятных критериев («если время отклика превышает заданное значение», «если количество ошибок за единицу времени выше, чем х» и т.д). Кстати, умеет отображать в реальном времени основные статистические данные теста, что бывает очень полезно прямо в процессе теста.
Танк используется и в самом Яндексе, и в других компаниях уже около 10 лет. Им обстреливают совершенно разные сервисы, с разными требованиями к сложности тестовых сценариев и к уровню нагрузки. Почти всегда для тестирования даже высоконагруженных сервисов хватает всего одного генератора нагрузки. Танк поддерживает разные генераторы нагрузки, как написанные специально для него (Phantom, BFG, Pandora), так и широко сторонние (JMeter). Модульная архитектура позволяет написать свой плагин под нужный генератор нагрузки и вообще прикрутить практически что угодно.
Для чего использовать разные генераторы нагрузки? Phantom — это быстрая «пушка» на C++. Один такой генератор может выдать до сотни тысяч запросов в секунду. Но для достижения такой скорости приходится генерировать запросы заранее и нельзя (не получается) использовать получаемые от тестируемого сервиса данные для генерации очередного запроса. В случаях, когда нужно исполнять сложный сценарий или сервис использует нестандартный протокол, следует использовать JMeter, BFG, Pandora.
В BFG, в отличие от Jmeter, нет GUI, тестовые сценарии пишутся на Python. Это позволяет использовать любые библиотеки (а их огромное количество). Часто бывает, что для сервиса написаны биндинги для Python, тогда их удобно использовать при написании нагрузочных сценариев. Pandora — это экспериментальная пушка на GoLang, достаточно быстрая и расширяемая, подходит для тестов по протоколу HTTP/2 и будет использоваться там, где нужны быстрые сценарии.
Внутри Яндекса для хранения и отображения результатов нагрузочных тестов используется специальный сервис. Сейчас наружу открыт его упрощенный аналог под названием Overload — он полностью бесплатен, его используют, в том числе, для тестирования открытых библиотек (например) и проведения соревнований.
Taurus
Taurus — ещё один фреймворк над несколькими утилитами нагрузочного тестирования. Возможно, вам понравится этот продукт, использующий похожий на Яндекс.Танк подход, но имеющий несколько другой набор «фич», и, пожалуй, более адекватный формат конфигурационных файлов.
Вообще, Taurus хорошо подойдёт в ситуации, когда мощность, скажем, Gatling важна для создания теста, но разбираться с Gatling (а также с написанием скриптов тестирования на Scala) нет желания или возможности: достаточно описать тест в куда более простом формате файла Taurus, настроить его на использование Gatling как инструмента создания нагрузки, и все Scala-файлы будут сгенерированы автоматически. Так сказать, «автоматизация автоматизации» в действии!
Taurus можно настроить на отправку статистики теста на онлайн-сервис BlazeMeter.com, который отобразит данные в виде нарядных графиков и таблиц. Подход не очень обычный, но заслуживающий внимания: движок вывода отчётов, очевидно, совершенствуется со временем, и постепенно будет выводить информацию ещё более симпатично.
Тестирование производительности
Тестировать производительность сервиса или приложения можно и нужно не только после завершения процесса разработки, но и во время неё, буквально так же, как мы делаем регулярные юнит- или регрессионные тесты. Правильно организованные, регулярные тесты производительности позволяют ответить на очень «тонкий» вопрос: не привели ли последние изменения в коде приложения к ухудшению производительности получившегося ПО?
Казалось бы, померить производительность — это так просто! Два раза взять timestamp (желательно с высокой точностью), посчитать разность, сложили-поделили, и всё — можно оптимизировать. Как бы не так! Хотя на словах этот вопрос звучит просто, на деле такого рода замеры довольно затруднительно производить, а сравнивать результаты разных замеров вообще не всегда разумно. Одна из причин: для сопоставления результатов тесты должны проходить над одними и теми же исходными данными, что, среди прочего, подразумевает воссоздание тестовой среды при каждом прогоне проверки, другая причина — сравнение субъективного восприятия времени работы тестового сценария может оказаться неточным.
Ещё одной причиной является сложность выделения влияния на производительность целого приложения работы отдельного его модуля, того, который мы сейчас правим. Усугубляя ситуацию, уточняем: ещё сложнее это влияние вычленить, если над кодом работает коллектив из более чем одного разработчика.
Один из подходов в такой ситуации состоит в тщательном создании полноценного тестового сценария, повторяющего работу с сервисом настоящего клиента, и прогоне его много раз, с параллельным анализом нагрузки на сервер, где проходит тестирование (таким образом, будет понятно, какая часть сценария создаёт нагрузку на отдельные ресурсы тестового сервера, что может дать дополнительную информацию по поиску мест, где следует подойти к производительности более серьёзно) — увы, не всегда можно такое позволить себе в реальной ситуации, просто потому, что объёмный тест, да ещё повторённый 10-20 раз, скорее всего будет слишком долгим, чтобы проводить его достаточно часто, а это полностью убьёт идею.
Второй подход, более подходящий к процессу разработки, заключается в организации ограниченного по масштабу, «микро-» или даже «нано-» тестирования отдельных мест кода (скажем, запуске одного метода или одной функции, но большое число раз — т.е., скорее, бенчмаркингу). Планирование такого тестирования требует дополнительных усилий со стороны разработчика, но результат окупается и общим улучшением производительности кода, и пониманием, как ведут себя отдельные части проекта по мере работы как над ними, так и над другими частями. Вот, для примера, пара инструментов для перформанс-тестирования:
JMH
JMH, или Java Microbenchmark Harness — это оснастка Java для сборки, запуска и анализа нано/микро/милли/макро-бенчмарков, написанных на Java и других языках с целевой платформой JVM. Сравнительно молодой фреймворк, в котором разработчики постарались учесть все нюансы JVM. Один из удобнейших инструментов, из тех, которые приятно иметь под рукой. JMH поддерживает следующие типы замеров: Throughput (замер чистой производительности), AverageTime (замер среднего времени выполнения), SampleTime (перцентиль времени исполнения), SingleShotTime (время вызова одного метода — актуально для замера «холодного» запуска тестируемого кода).
Поскольку речь идёт о Java, фреймворк учитывает в т.ч. и работу механизма кэширования JVM, и перед запуском бенчмарка несколько раз выполняет тестируемый код для «прогрева» кэша байт-кода Java-машины.
BenchmarkDotNet
BenchmarkDotNet берёт на себя рутинные действия при составлении бенчмарков для .NET-проектов и обеспечивает широкие возможности форматирования результатов ценой минимальных усилий. Как говорят авторы, фиче-реквестов хватает, поэтому BenchmarkDotNet есть куда развиваться.
На сегодняшний день BenchmarkDotNet — библиотека, в первую очередь, для бенчмарков, а не для перфоманс-тестов. Ведётся серьёзная работа над тем, чтобы библиотеку можно было также использовать на CI-сервере для автоматического детектирования перфомансных регрессий, но пока эти разработки не завершены.
Google Lighthouse
Замеры производительности фронтенда всегда стояли несколько особняком: с одной стороны, часто задержки связаны со скоростью реакции бэкенда, с другой — именно по поведению фронтенда (точнее, по скорости его реакции) пользователи часто судят о всём приложении, особенно, если речь идёт про веб.
В веб-фронтенде в отношении замеров производительности сейчас всё идёт в сторону использования Performance API и измерении именно тех параметров, которые имеют значение для конкретного проекта. Хорошим подспорьем окажется веб-сервис webpagetest.org с Performance API метками и замерами — он позволит увидеть картину не со своего компьютера, а с одной из многих точек тестирования, существующих в мире, и оценить влияние времени приёма-передачи данных через каналы интернет на работу фронтенда.
Этот продукт больше подходил бы для проверки страниц сайта на соответствие рекомендациям Google (и вообще best practices) как для веб-сайтов, так и для Progressive Web Apps, если бы не одна из его функций: среди проверок есть и тест на поведение сайта при плохом качестве веб-соединения, а также при полном отсутствии связи. Это не очень соотносится с перформанс-тестированием как таковым, однако, если задуматься, в некоторых случаях веб-приложение воспринимается «медленным» не потому, что медленно готовит данные, а потому, что условия его работы на машине пользователя, в его браузере, с учётом его соединения с интернетом — увы, не идеальны. Google Lighthouse как раз и позволяет оценить это влияние.
Да, тема бенчмарков и тестирования просто бесконечна. Про каждую из них можно и нужно писать пост, и не один. Однако, как мы с вами знаем, самым интересным будет не просто прочесть, а пообщаться, послушать, поспрашивать знающего человека, который, в силу своего опыта, заранее предупредит о многих мелких и крупных затруднениях, лежащих на пути освоения той или иной технологии.
Поэтому с радостью приглашаем вас посетить конференцию Гейзенбаг 2017 Moscow, которая состоится 8-9 декабря 2017 года, где будут, в частности, представлены доклады:
- Инструменты тестировщика
- Тестирование браузерной производительности web приложений (включая javascript, rendering, вот это всё)
- Flaky tests (доклад о нестабильных автотестах)
- TestContainers — интеграционное тестирование без мороки
Подробности и условия участия можно узнать на сайте конференции.
Автор: ValeriaKhokha