Облегчаем проведение нагрузочного тестирования с помощью Resource Object Model

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

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

Самой сложной частью может стать процесс планирования, потому что нам нужно думать о реальных сценариях, а не просто угадывать количество (виртуальных) пользователей, которых хотим имитировать. Важная часть процесса планирования — посмотреть на Кривую Гаусса и не забывать, что даже если тысячи пользователей используют приложение, какова вероятность того, что несколько пользователей нажмут на одну и ту же кнопку (попадут в один и тот же эндпоинт) в одно и то же время, исчисляемое миллисекундами?

Я только что сказал, что нагрузочное тестирование — это весело, и сразу же перешел к математике, вероятности и прочему, как скучно. (На самом деле нет!)

Resource Object Model нам в помощь

Недавно я был назначен на очень динамичный крупномасштабный проект, в котором мы использовали k6 для тестирования множества нефункциональных аспектов, таких как:

  • нагрузка, производительность, всплески и выносливость;

  • горизонтальная масштабируемость инфраструктуры;

  • аварийные сигналы Amazon CloudWatch;

  • блокировки баз данных;

  • мониторинг Prometheus.

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

Чтобы решить эту задачу и запустить тесты с 15 000+ виртуальных пользователей с одной машины, я использовал Resource Object Model. Это оказалось идеальным решением с минимальным потреблением ресурсов.

Работа со сценариями экстремального давления

Если пропустить некоторые части процесса планирования, все, что нам нужно, — это мощный инструмент, который поможет подвергнуть эндпоинты «экстремальному давлению». Под экстремальным давлением следует понимать 10 000 пользователей, которые одновременно или с небольшим шагом увеличивают нагрузку на один и тот же эндпоинт. Один инструмент на рынке действительно выделяется простотой и количеством виртуальных пользователей, которых мы можем генерировать на наших рабочих станциях, и это единственный и неповторимый Grafana k6.

Grafana k6 — это инструмент для проведения нагрузочного тестирования с открытым исходным кодом, который делает тестирование производительности простым и продуктивным для инженерных команд. Это бесплатный, гибкий, многофункциональный инструмент, ориентированный на разработчиков.

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

k6 разработан Grafana Labs и сообществом.

Покажите, как нагнетать давление в системе!

На сайте K6 Open Source представлена отличная документация и руководство по эксплуатации. Я лично с их помощью смог за день создать свой первый нагрузочный тест с имитацией 10 000 виртуальных пользователей.

Давайте воспользуемся общедоступным API Rick and Morty, чтобы продемонстрировать, как выглядит нагрузочный тест.

Примечание: Я использую API Rick and Morty только в демонстрационных целях, и я не выполнял приведенный ниже скрипт. Не подвергайте его экстремальной нагрузке, поскольку сайт/эндпоинт созданы не для этого!

import http from 'k6/http';
import { sleep, check } from 'k6';
import { Rate } from 'k6/metrics';

export const options = {
    vus: 10000,
    //duration: '10s',
};

export let errorRate = new Rate('errors');

export function setup() {
    console.log(">>>>>>>>>> STARTING <<<<<<<<<<<<");
}

export default function () {
    check(http.get('https://rickandmortyapi.com/api'), 
        { 
            'status was 200': (r) => r.status == 200 
        }
    );
    sleep(1);
    
    let allCharacters = http.get('https://rickandmortyapi.com/api/character');
    check(allCharacters, 
        { 
            'status was 200': (r) => r.status == 200 
        }
    );
    sleep(1);

    check(http.get('https://rickandmortyapi.com/api/character/'.concat(allCharacters.json().results[0].id)), 
        { 
            'status was 200': (r) => r.status == 200 
        }
    );
    sleep(1);

    let allLocations = http.get('https://rickandmortyapi.com/api/location/')
    check(allLocations, 
        { 
            'status was 200': (r) => r.status == 200 
        }
    );
    sleep(1);

    check(http.get('https://rickandmortyapi.com/api/location/'.concat(allLocations.json().results[0].id)), 
        { 
            'status was 200': (r) => r.status == 200 
        }
    );
    sleep(1);

    var allEpisodes = http.get('https://rickandmortyapi.com/api/episode/');
    check(allEpisodes, 
        { 
            'status was 200': (r) => r.status == 200 
        }
    );
    sleep(1);

    check(http.get('https://rickandmortyapi.com/api/episode/'.concat(allEpisodes.json().results[0].id)), 
        { 
            'status was 200': (r) => r.status == 200 
        }
    );
    sleep(1);
}

export function teardown(data) {
    console.log(">>>>>>>>>> TESTING COMPLETED <<<<<<<<<<<<");
}

Весь скрипт выглядит просто. По большей части мы отправляем запрос и проверяем HTTP-код ответа. Если нам понадобится добавить еще один тест, мы просто создадим новый скрипт, копируя существующий, поменяем эндпоинты API и вуаля — у нас есть новый тест. Звучит очень просто. Однако что произойдет, если команда разработчиков внесет изменения в бэкенд? Скрипты наверняка сфейлят, и нам придется вносить изменения непосредственно в файлы тестов. Еще хуже то, что некоторые ресурсы API используются в нескольких скриптах — придется вносить одинаковые изменения в несколько скриптов.

Реализация паттерна Resource Object Model

Для меня, пришедшего из мира автоматизации тестирования, первое, что пришло в голову, это попытаться реализовать в проекте нагрузочного тестирования какой-нибудь шаблон Page Object Model (Page Object Model используют, чтобы упростить работу с кодом тестов. Паттерн помогает отделить технические детали тест-кода от его логики. Так программа становится более читаемой.</p>" data-abbr="POM">POM), он же Resource Object Model (ROM). Я знаю, что это, скорее, анти-паттерн, а не реальный паттерн, но мой мозг подсказывает мне сделать это. Я всегда стремлюсь к единому источнику истины, одному месту для всех работ по сопровождению и скриптов, построенных, как Lego, из ранее созданных объектов. Возможно, ROM немного влияет на производительность тестов, но я предпочитаю DRY KISS (Do Not Repeat Yourself & Keep It Short and Simple).

Построение прочного фундамента

Идея создания прочного фундамента проекта основывается на чистой структуре папок/файлов/классов проекта. Давайте создадим такую же структуру папок, как в нашей документации по API:

Кроме того, важно иметь папку для хранения всех классов-хелперов, которые будут поддерживать потребности в тестировании. По этой причине я создал папку helper с baseClass.js, в которой будут реализованы общие методы для всех остальных классов, такие как:

  • Проверка кода состояния ответа API

  • Получение объекта результата

Создание моделей объектов ресурсов

Теперь пришло время создать ROM. Первая из них — "получить все эндпоинты ресурса".

GET https://rickandmortyapi.com/api

Этот ресурс реализует только метод GET, и наш класс ресурса должен реализовать только метод getEndpoints().

В случаях, когда на одном ресурсе реализовано несколько методов, таких как POST, PUT, DELETE и другие, мы не будем создавать новые классы, а добавим их как методы к существующему объекту ресурса с соответствующей реализованной логикой.

Следуя той же логике, теперь мы можем создавать и реализовывать и другие ресурсы.

GET https://rickandmortyapi.com/api/character

GET https://rickandmortyapi.com/api/episode

GET https://rickandmortyapi.com/api/location

Что дальше?

Пока все хорошо. Мы создали прочный фундамент для проекта нагрузочного тестирования. Чтобы завершить миссию по созданию простого и повторно используемого фреймворка для тестирования, нужно обратить внимание еще на пару моментов: варианты тестирования (тестовые сценарии) и настройку тестовой среды для выполнения сценариев по требованию. На мой взгляд, все это можно уместить в одном конфигурационном файле.

TestConfig JSON имеет два свойства: testScenario для исполнителя и настройки сценария и environment для URL среды и других настроек или возможностей, связанных со средой.

Создание первого теста ROM

В начале этой статьи я показал тест, созданный старым добрым способом. Теперь у нас есть все недостающие части для создания нового теста из повторно используемых объектов ROM. Посмотрите на новый тест на скриншоте ниже.

В начале теста мы устанавливаем значение option из свойств файла config.js. Мне кажется, это очень полезно, поскольку IntelliSense дает возможность выбирать из ранее настроенных сценариев тестирования. Та же логика применима к настройке окружения.

После настройки начальных опций и среды мы создаем экземпляры ROM, чьи методы вызываем в функции по умолчанию. Выглядит неплохо, верно? А лучше всего то, что тест имеет уникальный вид «установил и забыл».

Простота — это ключ

В конце концов, моя интуиция относительно ROM меня не подвела, и выбор в пользу простоты, а не сложности, привел к более оптимизированному нагрузочному тестированию. Я уверен, что есть еще хорошие способы достижения того же уровня поддерживаемости и возможности повторного использования. Поделитесь своими вариантами в комментариях. В конце концов, важно постоянно работать над улучшением работы и образа мышления.


Скоро состоится открытый урок, на котором рассмотрим инструменты shift left performance testing и сравним два наиболее популярных из них. Записаться на урок можно на странице онлайн-курса «Нагрузочное тестирование».

Источник: https://habr.com/ru/companies/otus/articles/733466/


Интересные статьи

Интересные статьи

Всем привет! В данной статье попробуем разобраться с проблемой N+1 (или может правильнее 1+N?) и как ее решить с помощью использования EntityGraph.Проблема N+1 возникает, когда мы генерируем запрос на...
В этой статье объясняем, зачем нужны инструменты автоматизации API и CLI и как с их помощью управлять инфраструктурой на примере Serverspace.
PHP является самым популярным языком для серверной разработки, по праву занимая первое место на рынке. Приложения многих всемирно известных организаций, таких как Facebook, написаны на PHP. WordPress,...
В этом туториале мы реализуем ~~Real World App~~ — подписку на обновления с помощью гугл таблиц, бессерверных функций и реакта. Основной функционал нашего приложения будет следующим: ...
Корни цифрового звука уходят глубоко в 70-е годы. Тогда японская телевизионная компания NHK проспонсировала производство PCM-рекордеров, которые задействовали во время эфиров. Уже позже с расп...