Как за 10 минут создать тестировщик нагрузки для API на Node.js

от автора

Вступительное слово

Немного объясню, зачем я вообще написал код для нагрузки на API и не воспользовался готовыми инструментами.

В своей работе я порой сталкиваюсь с задачами, которые, хоть и связаны с тестированием, но выходят за рамки моей специализации, например, тестирование производительности. Так, в один прекрасный день мне пришло задание нагрузить только что созданный GET-запрос, а именно — 50 rps в течение 20 секунд. Сначала я подумал сделать это в Postman, но в простой конфигурации можно указать только количество запросов и паузу между ними, а вкладка Performance встретила меня неприятным сообщением: «Couldn’t load form to set up performance test».

Следующим вариантом был JMeter. Хотя я делал с ним нагрузочные тесты, последний контакт с этим инструментом был аж 6 месяцев назад. Поэтому мне просто стало лень доставать его и вспоминать, как и что тут настраивать.

Вот тогда мне пришла идея написать собственный тестер нагрузки для API, который мог бы помочь QA-специалистам, не специализирующимся на нагрузочном тестировании, быстро решать аналогичные задачи, не углубляясь в сложные инструменты. По этой же причине я выбрал Node.js, так как он широко используется в современной разработке.

Реализация

Приступим к реализации. Для начала создадим папку проекта любым удобным для вас способом и инициализируем новый проект Node.js:

npm init -y

Затем устанавливаем необходимые зависимости:

npm install axios dotenv

Поскольку хорошие манеры предполагают выносить важные данные в переменные окружения, создаем в корне проекта файл .env и добавляем следующие переменные:

BASE_URL=<ваш_URL_API> TOKEN=<ваш_токен_доступа>  # Опционально, если требуется авторизация

Если вы планируете размещать проект в репозитории, не забудьте создать файл .gitignore и добавить в него строку .env, чтобы избежать случайного коммита данных из этого файла.

Создаем папку results, в которой будет храниться результат теста, а также файл testGet.js, в котором начнем писать наш код.

testGet.js для GET-запросов

Первым шагом подключаем необходимые модули:

  • dotenv для загрузки переменных окружения из файла .env

  • axios для отправки HTTP-запросов.

  • fs для работы с файловой системой.

  • path для работы с путями.

require('dotenv').config(); const axios = require('axios'); const fs = require('fs'); const path = require('path');

Задаем основные параметры для тестирования. Здесь параметры requestsPerSecond и durationInSeconds настраиваются исходя из ваших задач.

const url = process.env.BASE_URL; const token = process.env.TOKEN;  // Параметры теста, которые вы настраиваете исходя из своих потребностей const requestsPerSecond = 50; // Количество запросов в секунду const durationInSeconds = 20; // Продолжительность теста в секундах  const totalRequests = requestsPerSecond * durationInSeconds; let completedRequests = 0;

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

const resultsDir = path.join(__dirname, 'results'); if (!fs.existsSync(resultsDir)) {     fs.mkdirSync(resultsDir); }  const resultsFilePath = path.join(resultsDir, 'results.txt'); fs.writeFileSync(resultsFilePath, '');  // Флаг для записи результатов: true - все, false - только ошибки const logAllResponses = false;

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

Если достаточно чтобы все тесты были с одинаковыми параметрами:

const queryParams = [{ param1: 'value1', param2: 'valueA' }];

Если нужно разнообразить запросы:

const queryParams = [      { param1: 'value1', param2: 'valueA' },      { param1: 'value2', param2: 'valueB' },      { param1: 'value3', param2: 'valueC' },  ];

Если запрос вообще без параметров, оставляем пустой массив.

Для записи результатов создаем функцию logResponse. Она сохраняет в файл номер запроса, его статус, время выполнения и тело ответа:

const logResponse = (requestNumber, status, responseBody, timeTaken) => {     const logEntry = `Запрос ${requestNumber}\nСтатус: ${status}\nВремя ответа: ${timeTaken}ms\nТело ответа: ${JSON.stringify(responseBody)}\n\n`;     fs.appendFileSync(resultsFilePath, logEntry); };

Создаем асинхронную функцию sendRequest, которая выполняет HTTP-запрос:

  • Используем библиотеку axios.

  • Передаем токен для авторизации в заголовке.

  • Сохраняем время выполнения запроса.

  • В случае ошибки логируем статус и текст ошибки.

const sendRequest = async (params, requestNumber) => {     const startTime = Date.now();     try {         const response = await axios.get(url, {             headers: {                 'Authorization': `Bearer ${token}`             },             params: params         });                  const timeTaken = Date.now() - startTime;         if (logAllResponses) {             logResponse(requestNumber, response.status, response.data, timeTaken);         }                  completedRequests++;     } catch (error) {         const timeTaken = Date.now() - startTime;         let status = error.response ? error.response.status : 'Неизвестная ошибка';         let responseBody = error.response ? error.response.data : error.message;         logResponse(requestNumber, status, responseBody, timeTaken);     } };

Теперь реализуем основной цикл отправки запросов:

  • Используем setInterval для отправки определенного количества запросов в секунду.

  • Проверяем завершение теста и останавливаем интервал, если отправлено необходимое количество запросов.

  • Сохраняем итог теста в файл и выводим его в консоль.

const startTest = () => {     const interval = setInterval(() => {         for (let i = 0; i < requestsPerSecond; i++) {             if (completedRequests < totalRequests) {                 // Если есть параметры, используем их                 if (queryParams.length > 0) {                     const params = queryParams[i % queryParams.length];                     sendRequest(params, completedRequests + 1);                 } else {                     sendRequest(null, completedRequests + 1);                 }             }         }          if (completedRequests >= totalRequests) {             clearInterval(interval);             const summary = `Тест завершен. Отправлено ${completedRequests} запросов.\n`;             fs.appendFileSync(resultsFilePath, summary);             console.log(summary.trim());         }     }, 1000); };  // Запуск теста startTest();

Всё, наш тестировщик нагрузки для GET-запросов готов. Запустить его можно командой:
node testGet.js

testPost.js для POST-запросов

Хотя мое первоначальное желание ограничивалось скриптом для GET-запросов, но раз я собрался писать для Хабра, то решил не останавливаться на этом и дополнить его скриптом для POST-запросов.

Итак, создаем файл testPost.js и вставляем в него первую часть кода из testGet.js, заменив лишь имя текстового файла для записи результата на post_results.txt.

require('dotenv').config(); const axios = require('axios'); const fs = require('fs'); const path = require('path');  const url = process.env.BASE_URL; const token = process.env.TOKEN;  // Параметры теста, которые вы настраиваете исходя из своих потребностей const requestsPerSecond = 50; // Количество запросов в секунду const durationInSeconds = 20; // Продолжительность теста  const totalRequests = requestsPerSecond * durationInSeconds; let completedRequests = 0;  // Создание директории и файла для хранения результатов const resultsDir = path.join(__dirname, 'results'); if (!fs.existsSync(resultsDir)) {     fs.mkdirSync(resultsDir); }  const resultsFilePath = path.join(resultsDir, 'post_results.txt'); fs.writeFileSync(resultsFilePath, '');  // Флаг для записи результатов: true - все, false - только ошибки const logAllResponses = false;

Создадим функцию sendPostRequest, которая будет отвечать за отправку одного POST-запроса:

const sendPostRequest = async (data, requestNumber) => {     const startTime = Date.now();     try {         const response = await axios.post(url, data, {             headers: {                 'Authorization': `Bearer ${token}`,                 'Content-Type': 'application/json'             }         });          const timeTaken = Date.now() - startTime;         if (logAllResponses) {             logResponse(requestNumber, response.status, response.data, timeTaken);         }         completedRequests++;     } catch (error) {         const timeTaken = Date.now() - startTime;         const status = error.response ? error.response.status : 'Неизвестная ошибка';         const responseBody = error.response ? error.response.data : error.message;         logResponse(requestNumber, status, responseBody, timeTaken);     } };

И, наконец, функция для запуска теста с динамической генерацией данных. Динамические данные (например, id) можно генерировать по вашему усмотрению: это может быть простой Math.random, библиотека faker или что-то другое. Модернизацию кода я оставляю на ваше усмотрение.

const startPostTest = () => {     const dataTemplate = {         // если данные статичные, записывает как обычно:         key1: 'value1',          key2: 'value2',         // если данные динамичные, генерирует случайные значения, записывает так         key3: () => 'value3'         // где value3 например может быть Math.floor(Math.random() * 9) + 1     };      const generateData = (template) => {         if (Array.isArray(template)) {             return template.map(item => generateData(item));         } else if (typeof template === 'object' && template !== null) {             const result = {};             for (let key in template) {                 const value = template[key];                 if (typeof value === 'function') {                     result[key] = value();                 } else if (typeof value === 'object') {                     result[key] = generateData(value);                 } else {                     result[key] = value;                 }             }             return result;         } else {             return template;         }     };      const interval = setInterval(() => {         for (let i = 0; i < requestsPerSecond; i++) {             if (completedRequests < totalRequests) {                 const requestData = generateData(dataTemplate);                 sendPostRequest(requestData, completedRequests + 1);             }         }          if (completedRequests >= totalRequests) {             clearInterval(interval);              const summary = `Тест завершен. Отправлено ${completedRequests} POST-запросов.\n`;              fs.appendFileSync(resultsFilePath, summary);              console.log(summary.trim());         }     }, 1000); };  // Запуск теста startPostTest();

Запускаем этот скрипт можно командой:node testPost.js

Заключение

Этот функционал полностью подходит для решения небольших задач по нагрузочному тестированию API. Если кто-то обнаружит недостатки или баги, буду признателен за любую обратную связь.

Так же привожу ссылку на этот проект в Github


ссылка на оригинал статьи https://habr.com/ru/articles/870154/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *