用python写一个并发测试工具
工作中会有一些需要并发测试的场景,例如:两人同时操作一条数据,此时需要验证结果是否符合预期
最初是借助jmeter来进行并发测试,建2个线程组,每个线程组中各放一个接口,启动时会同时执行个线程组中的接口,从而实现并发测试的目的
但是每次都要打开jmeter,用起来不太方便,所以就尝试用python来实现接口并发测试,然后再写个前端页面
所以本次实现以下功能:
1、后端在django框架下,基于python实现接口并发测试
2、前端页面基于百度amis低代码平台实现
一、后端逻辑实现
用python自带的库concurrent.futures
,它提供ThreadPoolExecutor
,使之能够并发执行函数(来自ChatGPT)
ChatGPT给出的示例代码如下
上述代码中,make_request()
函数是需要并发执行的函数,ThreadPoolExecutor
设置并发请求,示例中传入的参数urls是一个list
根据自己的实际需求改造一下示例代码:
首先我的目标是对2个接口请求进行并发测试,所以会从前端传入2个接口的请求url、headers、请求参数,把前端参数组合成list,大致如下:列表包裹字典,一个字典是一组参数,标记好url、headers、payload
param_data = [
{"url": url_1, "headers": headers_1, "payload": payload_1},
{"url": url_2, "headers": headers_2, "payload": payload_2}
]
新建一个concurrency_test.py
文件
make_request()
函数改为如下形式,它接收的参数data就是一组字典参数
from concurrent.futures import ThreadPoolExecutor
import requests
def make_request(data):
r = requests.post(url=data["url"], headers=data["headers"], json=data["payload"], verify=False)
# print(r.json())
return r.json()
然后再定义一个主函数
def multi_processing(data):
with ThreadPoolExecutor(max_workers=2) as executor:
results = list(executor.map(make_request, data))
# print(results)
res = []
for i, result in zip(data, results):
res.append(
result["message"] + ";traceId=" + result["traceId"]
)
print(res)
return res
max_workers
控制并发数量(线程数),map()中接收并发函数以及它所需要的参数
再新建一个视图函数concurrency_test_views.py
from django.http import JsonResponse,HttpResponse
import json
from app.api.concurrency_test import *
from django.views.decorators.http import require_http_methods
def concurrent(request):
url_1 = request.GET.get("url_1")
headers_1 = json.loads(request.GET.get("header_1"))
payload_1 = json.loads(request.GET.get("body_1"))
url_2 = request.GET.get("url_2")
headers_2 = json.loads(request.GET.get("header_2"))
payload_2 = json.loads(request.GET.get("body_2"))
param_data = [
{"url": url_1, "headers": headers_1, "payload": payload_1},
{"url": url_2, "headers": headers_2, "payload": payload_2}
]
res = multi_processing(param_data)
dict_res = {
"res1": res[0],
"res2": res[1]
}
return JsonResponse(dict_res, json_dumps_params={'ensure_ascii': False})
# return HttpResponse(res)
最后配置好路由,这个接口就写好了
----------------------------------------------
接下来开始写前端,前端布局大致如下
这个页面是直接用amis写的,这部分是用的现成的组件,不再细说,贴下代码
前端amis代码
<template>
<div ref="box" style="text-align: center; font-size: 24px; font-weight: 700; margin-bottom: 20px">
基本信息
</div>
</template>
<script>
export default {
mounted() {
const amis = amisRequire('amis/embed')
let url = this.COMMON.test_url
let amisJSON = {
"type": "page",
"title": "工具类",
"remark": "",
"body": [
{
"type": "tabs",
"tabs": [
{
"title": "接口并发测试",
"hash": "tab1",
"body": [
{
"type": "grid",
"columns": [
{
// "type": "wrapper",
// "style": {
// "width": "600px"
// },
"body": [
// {
// "type": "wrapper",
// // "title": "面板标题",
// "body": "面板内容"
// },
{
"type": "form",
"style": {
},
"title": "并发测试工具",
"mode": "horizontal",
"autoFocus": false,
"horizontal": {
"leftFixed": "md"
},
"body": [
{
"type": "group",
"label": "Request URL",
"body": [
{
"type": "input-text",
"size": "", //控制输入框大小
"placeholder": "请输入第一个接口url",
"clearable": true, //设置可清除输入内容
"required": true,
// "showCounter": true,
// "maxLength": 11, //限制最大字数
"name": "url_1",
"value": "",
"label": false
},
{
"type": "input-text",
"size": "", //控制输入框大小
"placeholder": "请输入第二个接口url",
"clearable": true, //设置可清除输入内容
"required": true,
// "showCounter": true,
// "maxLength": 11, //限制最大字数
"name": "url_2",
"value": "",
"label": false
}
]
},
{
"type": "group",
"label": "Headers",
"body": [
{
"type": "textarea",
"size": "",
"placeholder": "请输入第一个接口的headers",
"required": true,
"clearable": true,
"name": "header_1",
"value": "",
"label": false
},
{
"type": "textarea",
"size": "",
"placeholder": "请输入第二个接口的headers",
"required": true,
"clearable": true,
"name": "header_2",
"value": "",
"label": false
}
]
},
{
"type": "group",
"label": "Payload",
"body": [
{
"type": "textarea",
"size": "",
"placeholder": "请输入第一个接口的请求参数",
"required": true,
"clearable": true,
"name": "body_1",
"value": "",
"label": false
},
{
"type": "textarea",
"size": "",
"placeholder": "请输入第二个接口的请求参数",
"required": true,
"clearable": true,
"name": "body_2",
"value": "",
"label": false
}
]
},
// json展示返回结果
{
"type": "control",
"css": {
".myClass": {
"color": "black",
"font-size": "15px",
"font-weight": "normal",
"text-align": "left",
// "display": "inline-block"
}
},
"name": "res",
"label": "接口返回结果",
"body": {
"type": "json",
"levelExpand": 100,
"className": "myClass"
}
}
// {
// "name": "res",
// "type": "static",
// "label": "返回结果",
// "value": ""
// }
],
"actions": [
{
"//": "type为submit时, 表示该按钮是一个行为按钮, 点击可以提交请求",
"type": "submit",
"label": "提交",
"//": "level配置按钮颜色, https://aisuda.bce.baidu.com/amis/zh-CN/components/action?page=1#%E4%B8%BB%E9%A2%98",
"level": "primary",
"api": {
"method": "get",
"url": url+"data_factory/concurrent",
"data": {
"url_1": "${url_1}",
"header_1": "${header_1}",
"body_1": "${body_1}",
"url_2": "${url_2}",
"header_2": "${header_2}",
"body_2": "${body_2}",
},
"adaptor": "return {\n data: {\n res: response.data\n}\n}"
}
},
{
"type": "reset",
"label": "重置"
}
]
}
]
}
]
},
{
"type": "grid",
"columns": [
{
"type": "page",
"title": "使用说明",
"css": {
".myClass": {
"color": "black",
"font-size": "15px",
"font-weight": "normal",
"text-align": "left",
"display": "inline-block"
}
},
"body": [{
"type": "tpl",
"tpl": "<ul><li>支持对接口进行并发测试</li><li>输入2个接口的请求url、headers、入参</li><li>【请求头】和【请求参数】均为json格式,示例如下</li></ul>",
"className": "myClass"
},
{
"type": "group",
"label": "Headers + Payload",
"body": [
{
"type": "textarea",
"value": '{\n "Content-Type":"application/json",\n "Cookie":"",\n "":"",\n "param": ""\n}',
"label": false
},
{
"type": "textarea",
"value": '{\n "Content-Type":"application/json;",\n "Cookie":"",\n "param":"",\n "param": ""\n}',
"label": false
},
{
"type": "textarea",
"value": '{\n "Content-Type":"application/json; charset=UTF-8",\n "Token":"",\n "param":"",\n}\n',
"label": false
},
{
"type": "textarea",
"value": '{\n "param":"",\n "param":""\n}\n \n',
"label": false
}
]
},
]
}
]
}
]
},
{
"title": "其他",
"hash": "tab5",
"body":
"开发中"
}
]
}
]
}
const amisScoped = amis.embed(this.$refs.box, amisJSON)
}
}
</script>