Перфоманс Лаб

нагрузочное тестирование
тестирование
2 июля, 2025

Заглушки на Python (Flask)

Время чтения: 5 мин.
2 июля, 2025
Автор:
Кириллов А.

Реализация проекта

Flask —

это микрофреймворк для создания веб-приложений на Python, который ценится за лёгкость, гибкость и минимализм.
Он широко применяется для разработки микросервисов, прототипов и простых веб-интерфейсов, где особенно важны скорость создания и адаптивность решений.

Зачем он нужен?

Когда нужно быстро «заглушить» метод, Flask отлично справляется с этой задачей.

По времени это сопоставимо с использованием WireMock Standalone, но с одним важным плюсом: в Flask можно легко реализовать ту логику, которая в WireMock невозможна (или слишком сложна), благодаря встроенным возможностям Python и его библиотекам.

Flask — это хорошее решение, если:

01
нужно заглушить много REST-методов за короткое время
02
предпочитаете Python
03
не хотите связываться с Java
04
цените простоту и удобство

Почему Flask — удобный инструмент для создания заглушек

У Flask есть всё необходимое, чтобы быстро настроить простой сервер и легко использовать его в разработке или тестировании.
01
Встроенная многопоточность позволяет обрабатывать несколько запросов одновременно без дополнительной настройки
02
Простота освоения делает Flask доступным даже для тех, кто сильно не погружен в  веб-разработку. С его помощью можно быстро развернуть рабочий HTTP-сервис с минимальным количеством кода
03
Flask предоставляет удобные средства для работы с заголовками и телами HTTP-запросов, что упрощает настройку ожидаемого поведения заглушки

Начало работы

01

Установите Python

Скачайте актуальную версию с официального сайта и установите её на свою систему
02

Установите Flask через pip

03

Выберите IDE на свой вкус

В этом гайде мы будем использовать Visual Studio Code — он лёгкий, удобный и хорошо работает с Python
04

Создайте рабочую папку с .ру файлом внутри

Это будет основной файл с кодом заглушки
При необходимости завернуть в контейнер

Содержание requirements.txt

Werkzeug==2.3.7
Flask==2.2.2
gunicorn==23.0.0
gevent==25.5.1

Содержание Dockerfile

FROM python:3.9
WORKDIR /backend
COPY requirements.txt /backend
RUN pip3 install --upgrade pip -r requirements.txt
COPY . /backend
EXPOSE 5000
ENTRYPOINT gunicorn --workers 9 --worker-class gevent -b 0.0.0.0:5000 app:app
Примерная структура проекта
структура проекта
Пример «привет мир» заглушки:

testmock.py

from flask import Flask, jsonify, request
import time, json, xml.etree.ElementTree as ET
app = Flask(__name__)
@app.route('/hello/world', methods=['GET'])
def test1():    
    time.sleep(0.1)
    return ("hello")
 
if __name__ == '__main__':
    app.run(host='0.0.0.0')

Каждый рест в заглушке — отдельный @app.route.
 Для эмулирования задержки используется time_sleep() (подается значение в секундах, 0.1 — это 100ms).

Для каждого реста должно быть уникальное название для вызываемого при запросе на сервер метода (тут test1()).
В methods можно написать несколько методов одновременно.

Запускаем python testmock.py, по умолчанию сервер отвечает по localhost:5000, проверяем командной строкой — curl localhost:5000/hello/world.

P.S.: большинство подключенных библиотек для такого реста, конечно же, не нужны — понадобятся позже.
P.P.S.: в реальном тестировании лучше запускать заглушку вот так:
gunicorn —workers * —worker-class gevent -b 0.0.0.0:5000 app:app

* — заменить на 2 * (кол-во CPU под заглушку) + 1

JSON, работа с файлами, корреляция

Техническое задание

Нужно заглушить метод /api/token, который будет принимать POST-запросы и возвращать заранее заданный JSON-ответ (например, из таблицы) со статусом 201.

Запрос

POST
/api/token
{
  "id": {произвольный id}
}

Ответ

{
   "access_token": "token",
   "expires_in": 1800,
   "respId": {id из запроса}
}
201

Создаём файл testresponse.json с шаблоном ответа.
Можно просто взять JSON из технического задания и либо удалить поле respId, либо присвоить ему любое значение.

Пример содержимого:

testresponse.json
{
   "access_token": "token",
   "expires_in": 1800,
   "respId": null
}

Перед REST-методами добавляем чтение файла в понятный Python JSON-объект response.

Чтение файла

with open('testresponse.json', 'r') as file:
    response1 = json.load(file)

Далее добавляем наш рест

/api/token

@app.route('/api/token', methods=['POST'])
def test2():
    requestBody = request.get_json(force=True)
    response1['respId'] = requestBody['id']
    time.sleep(0.1)
    return response1, 201

request.get_json(force=True)преобразует тело запроса в JSON-объект
response1[‘respId’] = requestBody[‘id’] вычитает значение поля id из запроса и присвоит его полю respId объекта response1, который вернется как ответ клиенту

Начинаем проверку

proverka

Имея базовые знания в Python, в заглушку можно добавить простую логику — например, возвращать разные коды страны в зависимости от первой цифры id из запроса.

Логика обработки

if str(requestBody['id'])[0]=='8':
    response1['ccode']='RU'
else: response1['ccode']='EU'

XML, работа с заголовками и параметрами из запроса

Техническое задание

Нам нужно заглушить метод /api/postbalance, который будет отвечать на POST-запросы ответом из таблицы

Запрос

POST
/api/postbalance?id={произвольный id}
<balance>
    <kpp>{произвольный kpp}</kpp>
</balance>
  headers:
 inn - {произвольный inn}

Ответ

<response>
    <inn>{inn из заголовка запроса}</inn>
    <kpp>{kpp из тела запроса}</kpp>
    <status>RECIEVED</status>
</response>
headers:
customer-id - {id из параметра запроса}
С XML немного сложнее, но принцип похожий.
 Сначала создаём шаблон ответа — файл xmltestresponse.xml с нужной структурой:
<response>
    <inn>null</inn>
    <kpp>null</kpp>
    <status>RECIEVED</status>
</response>
Дальше этот XML-шаблон преобразуем в объект ElementTree, чтобы удобно работать с ним в Python.

ET

response2 = ET.parse('xmltestresponse.xml').getroot()
Добавляем рест

/api/postbalance

@app.route('/api/postbalance', methods=['POST'])
def test3():
    requestBody = ET.fromstring(request.data)
    response2.find('kpp').text=requestBody.findtext('kpp')
    response2.find('inn').text=request.headers.get('inn')
    time.sleep(0.1)
    return ET.tostring(response2), {"customer-id": request.args.get('id'), "Content-Type": "application/xml"}

requestBody = ET.fromstring(request.data) — тело запроса преобразуется в строку, затем в объект ElementTree.
response2.find(‘kpp’).text = requestBody.findtext(‘kpp’) — kpp достаём методом ET из преобразованного запроса и подставляем в ET ответа.
response2.find(‘inn’).text = request.headers.get(‘inn’) — inn достаём из заголовка запроса и подставляем.
return ET.tostring(response2) — здесь ET преобразуем обратно в строку, так как Flask с ET не работает.
{«customer-id»: request.args.get(‘id’), «Content-Type»: «application/xml»} — подставляем в заголовок customer-id значение из параметра запроса, прописываем Content-Type, чтобы клиент не интерпретировал ответ сервера как строку.

Начинаем проверку

proverka 2

Проверка на производительность

Для оценки производительности мы провели сравнительный тест между Flask и WireMock Standalone. Специально для этого были подготовлены заглушки — /api/postbalance и /api/token — на платформе WireMock.

Нагрузка генерировалась с помощью JMeter, используя две идентичные тред-группы, которые постепенно увеличивали интенсивность запросов в течение всего теста. Одна группа направлялась на Flask-заглушки, вторая — на WireMock.

Обе системы были развернуты в контейнерах Docker с ограничением памяти в 4 ГБ на каждый экземпляр. В таких условиях ожидать высокой производительности не стоит, однако тест позволил выявить особенности и ограничения каждого решения в реальных условиях нагрузочного тестирования.

Результаты: Wiremock

rezultaty wiremock
rezultaty wiremock 2

WireMock выдержал нагрузку до 69,5 запросов в секунду (RPS), однако при достижении примерно 60 RPS начали появляться ошибки.
Среднее время отклика составило 401 мс для одного из методов и 244 мс для второго.

Результаты: Flask

rezultaty flask
rezultaty flask 2

Flask превзошёл WireMock по производительности, выдержав максимальную нагрузку в 74,6 запросов в секунду (RPS) — предел, заданный в нашем тестовом сценарии — без единой ошибки.

Среднее время отклика для обоих методов составило примерно 130 миллисекунд, что значительно быстрее результатов WireMock.

Закажите нагрузочное тестирование у тех, кто умеет достигать поставленных целей

Увеличьте производительность вашей системы до максимума и застрахуйте свой бизнес.