Итак, стоит честно признаться что ты нуб в тестировании. Потихоньку смотрю ролики на ютубе и пытаюсь эту нижу заполнить, в меру присутствия времени. Собственно в отношении мануального, ака ручного тестирования ничего значительно нового я не узнал. Все это производилось постоянно по факту написания кода и дебага. Автотесты — это прекрасно, но пока у меня руки… Хотя нет, скорее мозги не дошли до того как протестировать куски проекта в самом проекте автотестами. Там где есть какие-то проблемы, я, типично, тыркаю console.log, как раньше тыркал var_dump в php. Кстати, в последнем обложить тестами куски синхронного кода еще более менее можно представить. А вот теперь давайте представим сервер приложения, который ничего особенно сложного в качестве вычислений и преобразований не делает, а просто является restfull api к базе данных, причем выполняется асинхронно (js ведь) в нескольких процессах (кластер). Усложняясь правда тем, что на самом сервере реализована система анализа запроса и его кэширования. Собственно прототип схемы представлен ТУТ, хоть он уже немного и доработан.
Если визуально представить схему (заранее прошу прощения, т.к. кроме word-а под рукой ничего не было, чтобы схему нарисовать) выглядит она так:
Схема обработки веб-запросов (в общую схему не влезла, да и сюда поток ошибок я изобразил как ALL, чтобы не создавать «лапшу»):
Схемы приведены справочно и на данный момент они не совсем точно описывают апи. Однако, рисуя архитектуру приложения можно найти ошибки и узкие места, где может просесть производительность, так что свою роль данные схемы отыграли на ура.
Итак, нужно реализовать тестирование. Мануальные тесты проводились по каждой функции в процессе разработки и дебага, по всей api же тесты проводились единично RestManом и собственно клиентом. Ну и то, от чего хотелось бы уйти, тест прямо в продакшн. Ну а как иначе проводить нагрузочное тестирование?
Собственно данный процесс и было решено автоматизировать. Прежде всего можно выявить сразу два направления тестирования:
- тест на валидность данных
- тест на нагрузку, который можно выделить опять таки в два направления:
- тест на нагрузку системы кэширования
- тест на нагрузку системы обработки данных
Пункт 1 провожу парой клиентов, под нагрузкой только в продакшн — т.к. данное тестирование требует значительных ресурсов (необходимо проанализировать полученный объем данных, на пиках это было порядка 25Мбит/сек текстовых данных).
Автоматизировать буду пункт 2. Вот ТУТ я я рассказал о том как переписал hulk.py на javascript. Да, в ходе написания теста была найдена ошибка в модуле, а также он был доработан. Установить его вы можете из npm:
npm i @sergdudko/hulk
Данный тест может покрыть пункт 2.1. Вот что у меня вышло по выборке 1000 (нужно понимать что таймаут в исходниках модуля установлен в 5 секунд, если данных будет много — нужно его исправить. в моем случае 1000 документов сжатое gzip-ом с легкостью будет передано за это время) документов (первый запрос кэширующий — остальные из кэша):
С пунктом 2.2 уже сложнее, т.к. данный модуль его покрыть не может. Нужно было написать собственный тест на основе hulk, что я и сделал:
var hulkCore = require('@sergdudko/hulk').core; //hulkCore(set_you_link, set_this_data, set_this_method, set_req_total, set_req_in_min, stdout); //настройка var speed = 1200, //запросов в минуту time = 1; //минут на тест //начальные данные var data = {"find":{"type":{"$eq":"ЧегоТоТам.ГдеТоТам"}},"limit":1000, "skip":0}; var summary = {}; //суммирование результатов function summ(object){ var clearobject = JSON.parse(JSON.stringify(object)); for(const key in object){ if(typeof(summary[key]) === 'undefined'){ summary[key] = object[key]; } else { summary[key] = summary[key] + object[key]; } } } //запускаем в 100 потоков c разными запросами var ctrl = 0; const fin = 100; function Req(val){ data.skip = val; hulkCore('http://10.0.8.1:9999/v2/query', JSON.stringify(data), 'POST', parseInt(time*speed/100), parseInt(speed/100)).then(result=>{ summ(result); ctrl++; },err=>{console.log(err);ctrl++;}); } for(var i = 0; i < fin; i++){ setTimeout(Req, i*100, i); //разнесем запуск } //по достижению конца тестирования выведем информацию и завершим процесс function finallog(){ if(ctrl === fin){ console.log(summary); process.exit(0); } } //проверяем что достигнут конец тестирования setInterval(finallog, 1000);
Сам скрипт представляет из себя запуск 100 экземпляров hulk.core с разными запросами (в данном случае имеет место сдвиг по выборке/skip, что гарантирует разные результаты запроса). Для того чтобы получить итоговые результаты сделал resolve из hulk.core в виде результатов теста. По факту получения resolve от промиса hulk.core суммирую результаты в глобальном объекте. Для того, чтобы понять что все промисы отработали — внедряю счетчик. Ну и чтобы не извращаться вешаю запуск функции каждую секунду, которая проверит что все промисы отработали и если это так — выведет в консоль результирующий объект и завершит процесс. Настройки теста это
- speed — скорость запросов в минуту
- time — продолжительность теста в минутах
Позже сделал еще более автоматический тест, который запускат сервер на localhost, а также запускает тест, описанный выше, по нему.
Собственно теперь в пару кликов можно сэмулировать поведение сервера в боевых условиях что весьма удобно. И, конечно же, используя hulk-a нужно понимать что вы проводя тестирование вы можете улететь в бан firewall, забить таблицу маршрутизации роутера, исчерпать все доступные сокеты, исчерпать ресурсы канала и все эти случаи будут считаться ошибкой. Тем временем сервер может вполне нормально работать. Собственно тест в 100 потоков с такой же выборкой (1000 документов) показал что около 9% запросов были ошибочными. С другой машины сервер работал нормально, так что вполне вероятно что проблемы вызваны совсем не сервером. К тому же, достаточная нагрузка ни к каким сбоям не приводила (опуская n-ное количество соединений в очереди к БД, впрочем, очередь исчезала уже через минуту после завершения тестирования).