Тестирование производительности API с помощью K6

В этом туториале мы узнаем:

  • Что такое K6 и как оно работает
  • Что такое тесты производительности и как выполнять их
  • Как анализировать результаты

Тестирование производительности — это о том, как система реагирует на нагрузку. Проверяется стабильность и отзывчивость, “реактивность” системы. Оценивается надежность системы в целом, а также в критически важных точках. 

В этом туториале поработаем с такой штукой как k6, разработанной для нагрузочного тестирования простого API-интерфейса. 

Тесты K6 работают на облачной PaaS-платформе Heroku (желательно сразу посмотреть, что это). 

В конце мы научимся анализировать результаты тестов (что является ключевой частью нагрузочного тестирования).

Содержание

Что нам понадобится

  • Базовые навыки тестирования (желательно также почитать обзор о тестировании производительности)
  • Аккаунт на GitHub (который уже давно должен у тебя быть)
  • Аккаунт на CircleCI (об этом ниже)
  • Аккаунт на K6 Cloud

Использовать связку K6+CircleCI не обязательно в дальнейшем (хотя и желательно для новичка, потому что удобно); вообще k6 — с открытыми исходниками, и ее философия “platform-agnostic”.

Итак, K6 это опенсорсный фреймворк для тестирования производительности. Умеет “инкапсулировать юзабилити” и производительность. Есть удобные инструменты командной строки для запуска скриптов, и дашборд-панель анализа результатов.

Фреймворк k6 написан на языке goja — это не что иное как имплементация JavaScript (ES2015-ES6) на “чистом” Golang. Это значит, что можно писать скрипты для k6 на JavaScript — но синтаксис скриптов совместим только со стандартом Java Script ES2015.

Примечание. K6 работает не на движке Node.js, а через компилятор Go/JavaScript.

Переходим к делу.

Клонируем репозиторий

Сначала клонируем тестовое приложение из репозитория.

git clone https://github.com/CIRCLECI-GWP/api-performance-testing-with-k6.git

Далее устанавливаем k6 у себя на компьютере.

Устанавливаем K6

В отличие от стандартных JavaScript-модулей, k6 должен ставиться из менеджера пакетов. На MacOS работаем в HomeBrew, на Windows — в Chocolatey. Для других ОС есть соответствующие указания в документации k6. 

В данном туториале будем работать в MacOS. 

Итак, запускаем команду в Homebrew:

brew install k6

Это все, k6 установлен.

Пишем свой первый тест производительности

Итак, k6 поставился и вроде бы даже работает. Дальше смотрим, что там измеряет k6, и какие метрики применяются для наших тестов. 

Нагрузочное тестирование моделирует ожидаемую нагрузку системы, имитируя подключение к системе множества пользователей одновременно. В нашем случае будем моделировать подключение пользователей к API, за какие-то временнЫе промежутки.

Нагрузочное тестирование — начинаем

Тесты производительности измеряют “отзывчивость” и стабильность нашей небольшой API-подсистемы. В нашем примере мы также будем искать “тонкие места” системы, а также допустимые лимиты нагрузки.

В этом туториале будем запускать скрипты обработки тестового файла, создающего простое приложение-планировщик (“todo-приложение”) в уже существующем API на платформе Heroku

Чтобы у нас получился правильный нагрузочный тест, нужно в него включить еще другие факторы, в первую очередь: настройка количества виртуальных пользователей, количества запросов, и временнОго диапазона. В нашем случае сосредоточимся сначала на настройке количества подключающихся пользователей. Эта функция уже встроена в фреймворк, так как является, в принципе, основной в тестировании производительности, здесь все просто пока.

Для эмуляции этого, применяется концепция так называемых экзекуторов. Они “подают нагрузку” на движок k6 и оценивают, как срабатывают скрипты. Задается следующее:

  • Количество добавляемых пользователей
  • Количество запросов
  • Длительность теста
  • Трафик, полученный тестовым приложением

В нашем тесте будем применять экзекутор по методу, называемому в k6 rump-up. В этом методе k6 “ступенчато” добавляет виртуальных пользователей — далее будем называть их VUS ( = virtual users) и выполняет тестовый скрипт, пока не дойдет до заданного “пика”. Затем нагрузка так же ступенчато снижается.

Типичный пример применения концепции экзекуторов и виртуальных пользователей:

import http from 'k6/http';
import { check, group } from 'k6';
 
export let options = {
   stages: [
       { duration: '0.5m', target: 3 }, // simulate ramp-up of traffic from 1 to 3 virtual users over 0.5 minutes.
       { duration: '0.5m', target: 4}, // stay at 4 virtual users for 0.5 minutes
       { duration: '0.5m', target: 0 }, // ramp-down to 0 users
     ],
};
 
export default function () {
   group('API uptime check', () => {
       const response = http.get('https://todo-app-barkend.herokuapp.com/todos/');
       check(response, {
           "status code should be 200": res => res.status === 200,
       });
   });
 
   let todoID;
   group('Create a Todo', () => {
       const response = http.post('https://todo-app-barkend.herokuapp.com/todos/',
       {"task": "write k6 tests"}
       );
       todoID = response.json()._id;
       check(response, {
           "status code should be 200": res => res.status === 200,
       });
       check(response, {
           "response should have created todo": res => res.json().completed === false,
       });
   })
});

В этом скрипте объект options «ступенчато» увеличивает количество пользователей. Хотя экзекутор нами не задан по умолчанию, k6 сам определяет stages, durations и targets в объекте options, и определяет что экзекутор находится в состоянии ramping-vus.

В нашем скрипте целевая нагрузка (target) — это максимально 4 одновременных пользователя за период времени “1 минута 30 секунд”. Скрипт будет ступенчато поднимать количество пользователей, и выполнит так много запросов-итераций create-todo, насколько возможно за заданный период.

Другими словами, здесь будем пробовать подать так много запросов, как можно за указанное время, изменяя количество активных виртуальных пользователей. Статус этого теста зависит от количества успешных запросов за указанный период. Ниже показан график виртуальных пользователей за некий период.

Это иллюстрация ramp-up graph показывает как с ходом времени добавляются виртуальные пользователи, до достижения “пикового” значения пользователей. Это количество достигается на последнем этапе, когда виртуальные пользователи “завершают” свои запросы и “выходят” из системы. В этом туториале пиковое количество будет равно 4. Экзекутор ramping-vus — не единственный возможный для таких тестов, в k6 есть и другие экзекуторы, их выбор зависит от конкретного теста производительности.

Пример скрипта выше показывает, как можно применять k6 для описания теста, задания количества виртуальных пользователей; а также как создаются разные “допущения”-assertions при помощи check( ), в блоках group( ) в скрипте.

Примечание. Группы тестов в k6 предназначены для сочетания “большой нагрузочный скрипт” + “анализ результатов”. Checks — это assertions в группе: но они работают не так как другие assertions — они не останавливают выполнение нагрузочного теста, когда он выдал fail или pass.

Выполнение тестов производительности в k6

Теперь ты умеешь запускать нагрузочные тесты. Сейчас будем тестить лимиты бесплатного экаунта Heroku и Todo-приложения с API. Хотя это не идеальный сервер для нагрузочных тестов, он позволяет определить, когда и при каких условиях твоя система с API “достигает своего предела”. В данном туториале этот предел, “тонкое место где рвется” может оказаться как на хост-платформе Heroku, так и в нашем API. 

Итак, наш тест будет посылать многочисленные HTTP-запросы виртуальных пользователей (VUS) к Heroku. Начиная с одного виртуального пользователя и поднимаясь к 4, виртуальные пользователи создают ToDo-элементы, на протяжении 1 минуты 30 секунд.

Внимание. Мы делаем допущение, что бесплатный Heroku способен отрабатывать 4 одновременных сессии пользователей, взаимодействующих с API.

Запускаем тест, приведенный выше, командой в терминале:

k6 run create-todo-http-request.js

Когда тест выполнится, смотрим результаты:

Проверяем в результатах, что тест выполнялся на протяжении 1 минуты 30,3 секунд, и что все итерации теста прошли (passed). Можно также увидеть, что за период выполнения теста — 4 одновременных пользователя сделали в общей сумме 390 итераций (то есть завершили создание ToDo-элементов).

Другие метрики, полученные из данных:

  • Checks — количество завершенных check( )-допущений, объявленных в тесте (из них прошли все)
  • Общее количество http_reqs, количество всех отправленных HTTP-запросов (780 запросов)
  • Сумма общего количества итераций создания новых ToDo-элементов + HTTP-запросов проверки аптайм-состояния нашего Heroku-сервера

Этот тест был успешным, и мы уже умеем получить важную информацию о приложении. Однако мы еще не знаем, имеет ли система “точки надлома”, и будет ли она вести себя непредсказуемо при некоем количестве одновременных пользователей, нагружающих API. Это мы обсудим в следующей главе.

Добавление еще одного HTTP-запроса поможет определить, “поломается” ли Heroku-сервер, или “поломается” все-таки наше приложение с API. Мы снизим количество виртуальных пользователей и длительность теста, чтобы сохранить ресурсы системы. Эти настройки делаются в соответствующем разделе конфигурации; или можно просто “закомментировать” последние два этапа выполнения.

Примечание. “Поломать приложение” означает перегрузить систему до состояния, когда она возвращает ошибку, а не успешное выполнение — при этом не меняем выделенные ресурсы или код. То есть система возвращает ошибку, обрабатывая только изменение количества пользователей или количества запросов.

stages: [
       { duration: '0.5m', target: 3 }, // simulate ramp-up of traffic from 1 to 3 users over 0.5 minutes.
       // { duration: '0.5m', target: 4 }, -> Comment this portion to prevent execution
       // { duration: '0.5m', target: 0 }, -> Comment this portion to prevent execution

В этой конфигурации мы сократили длительность выполнения и уменьшили количество виртуальных пользователей. Будем запускать этот же тест, но добавим еще один HTTP-запрос, чтобы:

  • Подтянуть созданный ToDo
  • Проверить, что его todoID правильный
  • Проверить, что state (состояние) равно completed:false

Для выполнения этого теста производительности нам понадобится файл create-and-fetch-todo-http-request.js, который уже положен в корневую папку проекта в клонированном репозитории. Идем в терминал и запускаем команду:

k6 run create-and-fetch-todo-http-request.js

После завершения смотрим на результаты:

k6 результаты

Результаты показывают, что тест “уперся” в лимиты бесплатного Heroku dyno для нашего пробного приложения. Хотя эти запросы были успешными, не все из них вернулись вовремя, поэтому как видим некоторые в статусе fail. Всего 3 одновременных виртуальных пользователя, и выполняя тесты на протяжении 30 seconds, имеем 56 успешно завершенных итераций. Из этих 56 checks, 95,97% были успешными.

Failed-результаты связаны с получением tofoID создаваемых ToDo-элементов. Это может быть первый признак “узкого места” в нашем API. Затем, можем увидеть, что наших http_reqs всего 168 — это общее количество запросов создания todo-элементов, получения todo-элементов, и проверки аптайма Heroku-сервера:

56 requests for each iteration * 3 - each of every request item

Из этих результатов понятно, что метрики тестов производительности — достаточно нестандартные в тестировании систем и приложений. Эти результаты зависят от конфигурации сервера, архитектуры приложения, и, может быть, архитектуры внешних баз данных приложения. Наша цель — гарантировать, что “узкие места” найдены и исправлены. В будущем, нужно научиться находить пределы нагрузки, которая может быть применена к системе; значит нужно скорректировать архитектуру, с учетом резких скачков нагрузки (usage spikes) и задействования ресурсов на уровне “выше среднего”.

На предыдущем этапе мы тестировали приложение на пробном аккаунте на Heroku-сервере и анализировали результаты в командной строке. Теперь узнаем, что в k6 весьма удобно анализировать результаты тестов производительности в облачном дашборде с веб-интерфейсом.

Конфигурация облачного хранилища k6 и вывод результатов

Запуск тестов производительности k6 и получение результатов в терминал — отлично для ознакомления, но неплохо бы научиться “расшаривать” результаты всей команде. Функции анализа и шеринг — сильные места k6.

Конфигурируем дашборд k6. Входим в облако k6 с логином-паролем, созданными ранее. После этого находим свой токен доступа:

k6 dashboard

На странице API-токенов копируем этот токен, чтобы активировать возможность запускать тесты и расшаривать их.

Далее добавляем в переменные окружения k6_CLOUD_TOKEN. Запускаем команду в терминале:

export K6_CLOUD_TOKEN=<k6-cloud-token>

Примечание. Надо заменить “иллюстративное” значение в токене — на то, которое скопировано с дашборда.

Теперь выполняем свой нагрузочный тест, командой:

k6 run --out cloud create-and-fetch-todo-http-request.js

Параметр —out cloud дает команду k6 вывести результаты в облако. Тогда K6 при запуске теста автоматически создает дашборды с результатами. 

Дашборд “интерпретирует” результаты, делая их более удобными для просмотра и расшаривания, чем это все выглядит в терминале. Первые полученные метрики — общее количество запросов, и ход выполнения запросов виртуальными пользователями. Также есть метрика времени, когда виртуальные пользователи делали запросы.

k6 dashboard

На графике выше видно общее количество виртуальных пользователей и количество их запросов, и среднее время ответа на каждый запрос. Можно внимательнее посмотреть каждый запрос. В дашборде k6 это выглядит удобнее — применением groups( ) и checks( ), о которых говорилось в предыдущей главе.

Пробуем — выделяем вкладку Checks и фильтруем результаты в приятный вид.

k6 checks

Все запросы в тесте выводятся в хронологическом порядке. Также видим успехи и отказы в % соотношении, и среднее время ответа, и общее количество выполненных запросов.

Для проверки отдельных запросов и ответов — выделяем вкладку HTTP на той же странице “инсайтов”. Там видим все выполненные запросы, их время, и сравнение с другими запросами. Например, в примере ниже один запрос попал в 95%-ный интервал, а другой в 99%. Дашборд показывает максимальное количество времени, потраченное на самый длительный запрос, и стандартное отклонение. На странице показано почти все нужное QA-команде для поиска узких мест в производительности.

k6 dashboard

Также можно фильтровать отдельные запросы и уточнять их производительность, сравнивая со средней. Это полезно в поиске узких мест в базе данных, когда много пользователей запрашивает ресурс и он начинает “висеть”, иногда сразу в нескольких точках.

Подробнее о дашборде.

Далее переходим к тестам производительности с применением CircleCI, и настройке тестов при каждом деплое.

Настройка Git и передача в CircleCI

Внимание. Если уже клонирован репозиторий с проектом, можно пропустить эту часть. Она для справки — например как настраивать свой собственный будущий проект.

Инициализируем Git-репозиторий проекта, командой:

git init

Создаем .gitignore-файл в корневой папке. В этот файл добавляем node_modules, чтобы модули, сгенерированные npm, не добавлялись в remote-репозиторий. Дальше добавляем коммит и пушим свой проект на GitHub.

Входим в аккаунт CircleCI и переходим к дашборду Проекты. Можно вбирать репозиторий из списка всех репозиториев на GitHub, привязанных к твоему аккаунту. В данном примере имеем проект api-performance-testing-with-k6. В дашборде Проекты выбираем нужный проект, и задействуем существующую конфигурацию.

Примечание. После инициализации билда — пайплайн может упасть. Надо добавить измененный конфигурационный файл .circleci/config.yml на GitHub, чтобы билд мог правильно собраться.

Настройка CircleCI

Создаем папку .circleci в корневой, и добавляем файл config.yml. В нем конфигурация CircleCI для всех проектов. Выполняем свои тесты, пользуясь функцией orb в k6:

version: 2.1
orbs:
 k6io: k6io/test@1.1.0
workflows:
 load_test:
   jobs:
     - k6io/test:
         script: create-todo-http-request.js

Применяем third-party orb

Orb’ы в CircleCI — это реюзабельные пакеты YAML-конфигураций, сжимающие код в одну строчку.  Чтобы задействовать third-party-орбы, типа python@1.2, надо:

  • Активировать “настройки организации” (это можно сделать имея права администратора), или же
  • Запросить разрешение от администратора CircleCI в своей QA-команде.

Теперь проверяем, что тесты производительности в k6 успешно выполнились, и что они интегрировались с CircleCI.

k6

Далее — о метриках времени выполнения по каждому эндпоинту.

Оценка времени API-запроса 

Из предыдущих тестов видно, что архитектура теста важнее, чем то как система реагирует на нагрузки за заданное время. В K6 есть метрика под названием Trend, которая “кастомизирует” метрики для вывода в консоль и в облако. Trend определяет время каждого запроса к эндпоинту, настраивая тайминги. Вот так:

import { Trend } from 'k6/metrics';
 
const uptimeTrendCheck = new Trend('/GET API uptime');
const todoCreationTrend = new Trend('/POST Create a todo');
 
 
export let options = {
   stages: [
       { duration: '0.5m', target: 3 }, // simulate ramp-up of traffic from 0 to 3Vus
   ],
};
 
export default function () {
   group('API uptime check', () => {
       const response = http.get('https://todo-app-barkend.herokuapp.com/todos/');
       uptimeTrendCheck.add(response.timings.duration);
       check(response, {
           "status code should be 200": res => res.status === 200,
       });
   });

Итак, имплементируем метрику Trend. Делаем импорт ее из k6/metrics, затем прописываем каждый интересующий тренд. В этой части туториала нам нужно только проверить время ответа API при проверке аптайма, или при создании нового todo. После создания трендов, переходим к отдельным тестам и вставляем нужные данные в тренды.

Выполняем тест производительности создания todo, следующей командой:

k6 run create-todo-http-request.js

Смотрим в консоль. Наш тестовый файл в корневой папке клонированного репозитория. После того как тест выполнился на локальной машине, делаем коммит и пушим изменения на GitHub.

console

После завершения выполнения тестов — проверяем описания трендов, добавленных на предыдущем этапе. В каждом есть тайминги по отдельным запросам: минимальное, максимальное и среднее время выполнения, и их “попадание в интервал” в %.

Итоги 

В туториале мы знакомились с k6 и выполняли тесты производительности, создавая простейший тест создания todo-элемента и запуска на Heroku-сервере. Также мы проанализировали результаты в терминале и в облачном дашборде, и создали кастомные метрики и тренды. 

***

Еще о тестировании производительности:

Тестирование производительности в Postman
Тестирование производительности веб-сервисов — теория и инструменты
Топ-15 бесплатных инструментов для нагрузочного тестирования

***

Источник 

Какой была ваша первая зарплата в QA и как вы искали первую работу?

Мега обсуждение в нашем телеграм-канале о поиске первой работы. Обмен опытом и мнения.

Подписаться
Уведомить о
guest

0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии

Мы в Telegram

Наш официальный канал
Полезные материалы и тесты
Готовимся к собеседованию
Project- и Product-менеджмент

? Популярное

? Telegram-обсуждения

Наши подписчики обсуждают, как искали первую работу в QA. Некоторые ищут ее прямо сейчас.
Наши подписчики рассказывают о том, как не бояться задавать тупые вопросы и чувствовать себя уверенно в новой команде.
Обсуждаем, куда лучше податься - в менеджмент или по технической ветке?
Говорим о конфликтных ситуациях в команде и о том, как их избежать
$1100*
медианная зарплата в QA в июне 2023

*по результатам опроса QA-инженеров в нашем телеграм-канале

Собеседование

19%*
IT-специалистов переехало или приняло решение о переезде из России по состоянию на конец марта 2022

*по результатам опроса в нашем телеграм-канале

live

Обсуждают сейчас