接口压力测试、性能测试工具

news/2024/11/17 14:53:02/

接口压力测试、性能测试工具

    • 文章说明
    • 核心源码
      • 1.0版本--采用浏览器发送ajax请求进行性能测试
      • 2.0版本--结合Java模拟压力测试功能
    • 运行截图
    • 源码下载

文章说明

使用jmeter有些地方我觉得有点小复杂,我写了一个小工具来进行接口的简单性能和压力测试

核心源码

1.0版本–采用浏览器发送ajax请求进行性能测试

使用到的核心技术主要包含以下几点:
1、采用队列进行请求的逐个执行
2、采用 requestIdleCallback 进行页面的部分渲染优化

接口测试页面

<script setup>
import {computed, onBeforeMount, reactive, ref} from "vue";
import RequestItem from "@/component/RequestItem.vue";
import {deleteRequest, getRequest, message, postRequest, putRequest} from "@/util";
import {openRecordLog} from "@/util/config";
import {dbOperation} from "@/util/dbOperation";const data = reactive({interfaceList: [],selectInterface: {method: "",url: "",},method: null,form: {selectInterfaceId: null,body: "",threadNumber: 1,eachRequestTimesForThread: 1,},rules: {selectInterfaceId: [{required: true,trigger: "change",message: "请选择测试接口",},],threadNumber: [{required: true,trigger: "blur",message: "请输入线程数量",},],eachRequestTimesForThread: [{required: true,trigger: "blur",message: "请输入每个线程请求次数",},],},resultGroupList: [],testing: false,startTime: 0,endTime: 0,
});onBeforeMount(() => {const interfaceList = JSON.parse(localStorage.getItem("interfaceList"));if (interfaceList) {for (let i = 0; i < interfaceList.length; i++) {data.interfaceList.push(interfaceList[i]);}}
});function updateSelectInterface() {for (let i = 0; i < data.interfaceList.length; i++) {if (data.form.selectInterfaceId === data.interfaceList[i].id) {data.selectInterface = {method: data.interfaceList[i].method,url: data.interfaceList[i].url,};data.method = data.selectInterface.method;break;}}
}const formRef = ref();function judgeInteger(number) {const reg = /^\+?[1-9][0-9]*$/;return reg.test(number);
}const total = computed(() => {if (!judgeInteger(data.form.threadNumber) || !judgeInteger(data.form.eachRequestTimesForThread)) {message("线程数量、每个线程请求次数 需要为整数", "warning");return 0;}return parseInt(data.form.threadNumber) * parseInt(data.form.eachRequestTimesForThread);
});const success = computed(() => {let count = 0;for (let i = 0; i < data.resultGroupList.length; i++) {count += data.resultGroupList[i].success;}return count;
});const totalAverage = computed(() => {let countOfTotal = 0;let countOfSuccess = 0;for (let i = 0; i < data.resultGroupList.length; i++) {countOfTotal += data.resultGroupList[i].total;countOfSuccess += data.resultGroupList[i].success;}return (countOfTotal / countOfSuccess).toFixed(2);
});// 并发控制参数
const MAX_CONCURRENT_REQUESTS = 5; // 设置最大并发请求数
let runningRequests = 0; // 当前运行中的请求数
const requestQueue = []; // 请求队列// 执行请求
function enqueueRequest(resultGroup, requestItem) {requestQueue.push({resultGroup, requestItem});processQueue();
}// 处理队列
function processQueue() {while (runningRequests < MAX_CONCURRENT_REQUESTS && requestQueue.length > 0) {const {resultGroup, requestItem} = requestQueue.shift();runningRequests++;executeRequest(resultGroup, requestItem).finally(() => {runningRequests--;processQueue(); // 完成一个请求后,检查队列并启动下一个请求});}
}async function executeRequest(resultGroup, requestItem) {let response;try {requestItem.createTime = Date.now();switch (requestItem.method) {case "get":response = await getRequest(requestItem.url);break;case "post":response = await postRequest(requestItem.url, requestItem.body);break;case "put":response = await putRequest(requestItem.url, requestItem.body);break;case "delete":response = await deleteRequest(requestItem.url, requestItem.body);break;}const now = Date.now();requestItem.response = JSON.stringify(response.data);requestItem.spend = now - requestItem.createTime;requestItem.loading = false;resultGroup.success++;resultGroup.total += requestItem.spend;data.endTime = Date.now();await openRecordLog();await dbOperation.add([{url: requestItem.url,method: requestItem.method,body: requestItem.body,response: requestItem.response,create_time: requestItem.createTime,spend: requestItem.spend,},]);} catch (e) {console.error(e);}
}// 时间切片处理
function processRequestsInChunks(chunks) {if (chunks.length === 0) {data.testing = false;return;}requestIdleCallback((deadline) => {while (deadline.timeRemaining() > 1 && chunks.length > 0) {const [resultGroup, requestItems] = chunks[0];let j;for (j = 0; j < requestItems.length; j++) {const requestItem = requestItems[j];enqueueRequest(resultGroup, requestItem);}requestItems.splice(0, j);if (requestItems.length === 0) {chunks.shift();}}processRequestsInChunks(chunks);}, {timeout: 5000});
}function test() {if (data.testing) {message("当前正在测试", "warning");return;}formRef.value.validate((valid) => {if (valid) {data.testing = true;data.startTime = Date.now();if (!judgeInteger(data.form.threadNumber) || !judgeInteger(data.form.eachRequestTimesForThread)) {message("线程数量、每个线程请求次数 需要为整数", "warning");return;}data.resultGroupList = [];const chunks = [];for (let i = 0; i < data.form.threadNumber; i++) {const resultGroup = reactive({groupId: Date.now() + "__" + (i + 1),groupLabel: "线程" + (i + 1),requestList: [],total: 0,success: 0,});data.resultGroupList.push(resultGroup);const requestItems = [];for (let j = 0; j < data.form.eachRequestTimesForThread; j++) {const requestItem = reactive({requestId: j + 1,url: data.selectInterface.url,method: data.selectInterface.method,body: data.form.body,response: "",createTime: 0,spend: 0,loading: true,});requestItems.push(requestItem);resultGroup.requestList.push(requestItem);}chunks.push([resultGroup, requestItems]);}// 开始时间切片处理processRequestsInChunks(chunks);}});
}
</script><template><el-form ref="formRef" :model="data.form" :rules="data.rules" label-position="right" label-width="auto"><el-form-item label="测试接口:" prop="selectInterfaceId"><el-select v-model="data.form.selectInterfaceId" placeholder="请选择测试接口" @change="updateSelectInterface"><template v-for="item in data.interfaceList" :key="item.id"><el-option :label="item.url + '__' + item.method" :value="item.id"></el-option></template></el-select></el-form-item><el-form-item v-if="data.method && data.method !== 'get'" label="请求报文:"><el-input v-model="data.form.body" :rows="10" placeholder="请输入请求报文" type="textarea"/></el-form-item><el-form-item label="线程数量:" prop="threadNumber"><el-input v-model="data.form.threadNumber" placeholder="请输入线程数量"/></el-form-item><el-form-item label="每个线程请求次数:" prop="eachRequestTimesForThread"><el-input v-model="data.form.eachRequestTimesForThread" placeholder="请输入每个线程请求次数"/></el-form-item><el-form-item><div style="width: 100%; display: flex; justify-content: center"><el-button type="danger" @click="test">测试</el-button></div></el-form-item></el-form><el-divider/><h3 style="margin-bottom: 20px"><span>测试结果展示:</span><el-tag type="primary" style="margin-right: 10px">请求总数:{{ total }}</el-tag><el-tag type="success" style="margin-right: 10px">已完成:{{ success }}</el-tag><el-tag type="info" style="margin-right: 10px">请求开始:{{ data.startTime }}</el-tag><el-tag type="info" style="margin-right: 10px">请求结束:{{ data.endTime }}</el-tag><el-tag type="info" style="margin-right: 10px">请求总耗时:{{ data.endTime - data.startTime }}</el-tag><el-tag type="info" style="margin-right: 10px">平均耗时:{{ ((data.endTime - data.startTime) / success).toFixed(2) }}</el-tag><el-tag type="info">请求平均耗时:{{ totalAverage }}</el-tag></h3><el-tabs style="height: fit-content" type="border-card"><template v-for="resultGroup in data.resultGroupList" :key="resultGroup.groupId"><el-tab-pane :label="resultGroup.groupLabel" lazy><h3 style="margin-bottom: 20px"><span>请求结果展示:</span><el-tag type="primary" style="margin-right: 10px">请求总数:{{ resultGroup.requestList.length }}</el-tag><el-tag type="success" style="margin-right: 10px">已完成:{{ resultGroup.success }}</el-tag><el-tag type="info" style="margin-right: 10px">请求总耗时:{{ resultGroup.total }}</el-tag><el-tag type="info">平均耗时:{{ (resultGroup.total / resultGroup.success).toFixed(2) }}</el-tag></h3><el-collapse><template v-for="item in resultGroup.requestList" :key="item.requestId"><RequestItem :body="item.body" :create-time="item.createTime" :loading="item.loading" :method="item.method":request-id="item.requestId" :response="item.response" :spend="item.spend" :url="item.url"/></template></el-collapse></el-tab-pane></template></el-tabs>
</template><style lang="scss" scoped></style>

2.0版本–结合Java模拟压力测试功能

Java端开启线程进行http请求执行

package com.boot.util;import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.boot.entity.Request;
import com.boot.entity.RequestItem;
import com.boot.entity.Result;
import com.boot.entity.ResultGroup;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.java_websocket.WebSocket;import java.util.List;
import java.util.Map;/*** @author bbyh* @date 2024/11/12 15:03*/
@Data
@AllArgsConstructor
public class HandleHttpThread implements Runnable {private final Request request;private final WebSocket webSocket;@Overridepublic void run() {Map<String, Object> body;if (request.getBody() != null && !"".equals(request.getBody())) {body = JSONUtil.parseObj(request.getBody());} else {body = null;}List<ResultGroup> resultGroupList = request.getResultGroupList();for (ResultGroup resultGroup : resultGroupList) {String groupId = resultGroup.getGroupId();List<RequestItem> requestList = resultGroup.getRequestList();new Thread(() -> {for (RequestItem requestItem : requestList) {Integer requestId = requestItem.getRequestId();long start = System.currentTimeMillis();Result result = HttpClientUtil.normal(request.getUrl(), body, null, request.getMethod());long end = System.currentTimeMillis();JSONObject msg = new JSONObject();msg.putOnce("groupId", groupId);msg.putOnce("requestId", requestId);msg.putOnce("spend", end - start);msg.putOnce("response", JSONUtil.toJsonStr(result));synchronized (webSocket) {webSocket.send(JSONUtil.toJsonStr(msg));}}}).start();}}
}

前台进行websocket获取http请求执行的返回消息

<script setup>
import {computed, nextTick, onBeforeMount, reactive, ref} from "vue";
import RequestItem from "@/component/RequestItem.vue";
import {message} from "@/util";
import {openRecordLog} from "@/util/config";
import {dbOperation} from "@/util/dbOperation";
import {MyWebSocket} from "@/util/myWebSocket";const data = reactive({interfaceList: [],selectInterface: {method: "",url: "",},method: null,form: {selectInterfaceId: null,body: "",threadNumber: 1,eachRequestTimesForThread: 1,},rules: {selectInterfaceId: [{required: true,trigger: "change",message: "请选择测试接口",},],threadNumber: [{required: true,trigger: "blur",message: "请输入线程数量",},],eachRequestTimesForThread: [{required: true,trigger: "blur",message: "请输入每个线程请求次数",},],},resultGroupList: [],testing: false,startTime: 0,endTime: 0,
});onBeforeMount(() => {const interfaceList = JSON.parse(localStorage.getItem("interfaceList"));if (interfaceList) {for (let i = 0; i < interfaceList.length; i++) {data.interfaceList.push(interfaceList[i]);}}
});function updateSelectInterface() {for (let i = 0; i < data.interfaceList.length; i++) {if (data.form.selectInterfaceId === data.interfaceList[i].id) {data.selectInterface = {method: data.interfaceList[i].method,url: data.interfaceList[i].url,};data.method = data.selectInterface.method;break;}}
}const formRef = ref();function judgeInteger(number) {const reg = /^\+?[1-9][0-9]*$/;return reg.test(number);
}const total = computed(() => {if (!judgeInteger(data.form.threadNumber) || !judgeInteger(data.form.eachRequestTimesForThread)) {message("线程数量、每个线程请求次数 需要为整数", "warning");return 0;}return parseInt(data.form.threadNumber) * parseInt(data.form.eachRequestTimesForThread);
});const success = computed(() => {let count = 0;for (let i = 0; i < data.resultGroupList.length; i++) {count += data.resultGroupList[i].success;}return count;
});const totalAverage = computed(() => {let countOfTotal = 0;let countOfSuccess = 0;for (let i = 0; i < data.resultGroupList.length; i++) {countOfTotal += data.resultGroupList[i].total;countOfSuccess += data.resultGroupList[i].success;}return (countOfTotal / countOfSuccess).toFixed(2);
});let groupMap = {};
let requestItemMap = {};function test() {if (data.testing) {message("当前正在测试", "warning");return;}formRef.value.validate((valid) => {if (valid) {data.testing = true;data.startTime = Date.now();groupMap = {};requestItemMap = {};if (!judgeInteger(data.form.threadNumber) || !judgeInteger(data.form.eachRequestTimesForThread)) {message("线程数量、每个线程请求次数 需要为整数", "warning");return;}data.resultGroupList = [];for (let i = 0; i < data.form.threadNumber; i++) {const resultGroup = reactive({groupId: Date.now() + "__" + (i + 1),groupLabel: "线程" + (i + 1),requestList: [],total: 0,success: 0,});data.resultGroupList.push(resultGroup);groupMap[resultGroup.groupId] = resultGroup;for (let j = 0; j < data.form.eachRequestTimesForThread; j++) {const requestItem = reactive({requestId: j + 1,url: data.selectInterface.url,method: data.selectInterface.method,body: data.form.body,response: "",createTime: 0,spend: 0,loading: true,});resultGroup.requestList.push(requestItem);requestItemMap[resultGroup.groupId + "__" + requestItem.requestId] = requestItem;}}const socket = new MyWebSocket(8082, function (event) {handleHttp(event);}, function () {socket.send({request: {url: data.selectInterface.url,method: data.selectInterface.method,body: data.form.body,resultGroupList: getResultGroupList(),}});});}});
}async function handleHttp(event) {const transferData = JSON.parse(event.data);const groupId = transferData.groupId;const spend = transferData.spend;const response = transferData.response;groupMap[groupId].total += spend;groupMap[groupId].success++;const requestId = groupId + "__" + transferData.requestId;const createTime = Date.now() - spend;requestItemMap[requestId].createTime = createTime;requestItemMap[requestId].spend = spend;requestItemMap[requestId].response = response;requestItemMap[requestId].loading = false;data.endTime = Date.now();await openRecordLog();await dbOperation.add([{url: data.selectInterface.url,method: data.selectInterface.method,body: data.form.body,response: response,create_time: createTime,spend: spend,},]);await nextTick(() => {if (success.value === total.value) {data.testing = false;}});
}function getResultGroupList() {const resultGroupList = [];for (let i = 0; i < data.resultGroupList.length; i++) {const resultGroup = {groupId: data.resultGroupList[i].groupId,requestList: [],};for (let j = 0; j < data.resultGroupList[i].requestList.length; j++) {const requestItem = {requestId: data.resultGroupList[i].requestList[j].requestId,};resultGroup.requestList.push(requestItem);}resultGroupList.push(resultGroup);}return resultGroupList;
}
</script><template><el-form ref="formRef" :model="data.form" :rules="data.rules" label-position="right" label-width="auto"><el-form-item label="测试接口:" prop="selectInterfaceId"><el-select v-model="data.form.selectInterfaceId" placeholder="请选择测试接口" @change="updateSelectInterface"><template v-for="item in data.interfaceList" :key="item.id"><el-option :label="item.url + '__' + item.method" :value="item.id"></el-option></template></el-select></el-form-item><el-form-item v-if="data.method && data.method !== 'get'" label="请求报文:"><el-input v-model="data.form.body" :rows="10" placeholder="请输入请求报文" type="textarea"/></el-form-item><el-form-item label="线程数量:" prop="threadNumber"><el-input v-model="data.form.threadNumber" placeholder="请输入线程数量"/></el-form-item><el-form-item label="每个线程请求次数:" prop="eachRequestTimesForThread"><el-input v-model="data.form.eachRequestTimesForThread" placeholder="请输入每个线程请求次数"/></el-form-item><el-form-item><div style="width: 100%; display: flex; justify-content: center"><el-button type="danger" @click="test">测试</el-button></div></el-form-item></el-form><el-divider/><h3 style="margin-bottom: 20px"><span>测试结果展示:</span><el-tag type="primary" style="margin-right: 10px">请求总数:{{ total }}</el-tag><el-tag type="success" style="margin-right: 10px">已完成:{{ success }}</el-tag><el-tag type="info" style="margin-right: 10px">请求开始:{{ data.startTime }}</el-tag><el-tag type="info" style="margin-right: 10px">请求结束:{{ data.endTime }}</el-tag><el-tag type="info" style="margin-right: 10px">请求总耗时:{{ data.endTime - data.startTime }}</el-tag><el-tag type="info" style="margin-right: 10px">平均耗时:{{ ((data.endTime - data.startTime) / success).toFixed(2) }}</el-tag><el-tag type="info">请求平均耗时:{{ totalAverage }}</el-tag></h3><el-tabs style="height: fit-content" type="border-card"><template v-for="resultGroup in data.resultGroupList" :key="resultGroup.groupId"><el-tab-pane :label="resultGroup.groupLabel" lazy><h3 style="margin-bottom: 20px"><span>请求结果展示:</span><el-tag type="primary" style="margin-right: 10px">请求总数:{{ resultGroup.requestList.length }}</el-tag><el-tag type="success" style="margin-right: 10px">已完成:{{ resultGroup.success }}</el-tag><el-tag type="info" style="margin-right: 10px">请求总耗时:{{ resultGroup.total }}</el-tag><el-tag type="info">平均耗时:{{ (resultGroup.total / resultGroup.success).toFixed(2) }}</el-tag></h3><el-collapse><template v-for="item in resultGroup.requestList" :key="item.requestId"><RequestItem :body="item.body" :create-time="item.createTime" :loading="item.loading" :method="item.method":request-id="item.requestId" :response="item.response" :spend="item.spend" :url="item.url"/></template></el-collapse></el-tab-pane></template></el-tabs>
</template><style lang="scss" scoped></style>

运行截图

接口列表
在这里插入图片描述

接口测试
在这里插入图片描述

测试记录
在这里插入图片描述

测试记录详情
在这里插入图片描述

源码下载

接口性能测试工具


http://www.ppmy.cn/news/1547743.html

相关文章

python习题练习

python习题 编写一个简单的工资管理程序系统可以管理以下四类人:工人(worker)、销售员(salesman)、经理(manager)、销售经理(salemanger)所有的员工都具有员工号&#xff0c;工资等属性&#xff0c;有设置姓名&#xff0c;获取姓名&#xff0c;获取员工号&#xff0c;计算工资等…

怎样遵守编程规范,减少和控制C++编程中出现的bug?

遵守编程规范和最佳实践是减少和控制 C 编程中出现 bug 的重要手段。以下是一些具体的建议和策略&#xff0c;帮助你编写更健壮、更易于维护的 C 代码。 1. 遵循 C 标准和最佳实践 使用现代 C 特性&#xff1a;尽可能使用 C11 及之后的标准&#xff0c;避免使用过时的特性和库…

SpringBoot整合FreeMarker生成word表格文件

SpringBoot整合FreeMarker生成word表格文件&#xff08;使用FTL模板&#xff09;_freemarker ftl模板-CSDN博客 Freemarker基本指令语法和集合指令语法SpringBoot整合FreeMarker生成word表格文件&#xff08;使用FTL模板&#xff09;_freemarker ftl模板-CSDN博客https://zhua…

【MySQL】MySQL中的函数之JSON_REPLACE

在 MySQL 中&#xff0c;JSON_REPLACE() 函数用于在 JSON 文档中替换现有的值。如果指定的路径不存在&#xff0c;则 JSON_REPLACE() 不会修改 JSON 文档。如果需要添加新的键值对&#xff0c;可以使用 JSON_SET() 函数。 基本语法 JSON_REPLACE(json_doc, path, val[, path,…

数据结构(单向链表——c语言实现)

链式存储的优缺点&#xff1a; 优点&#xff1a; 1、动态分配内存&#xff1a; 链式存储不需要在数据插入之前分配固定大小的数组或内存块&#xff0c;因此它更适合存储动态变化的数据 2、高效的插入和删除操作&#xff1a; 在链表中插入或删除元素只需要调整相邻节点的指…

.NET 9 中 IFormFile 的详细使用讲解

在.NET应用程序中&#xff0c;处理文件上传是一个常见的需求。.NET 9 提供了 IFormFile 接口&#xff0c;它可以帮助我们轻松地处理来自客户端的文件上传。以下是 IFormFile 的详细使用讲解。 IFormFile 接口简介 IFormFile 是一个表示上传文件的接口&#xff0c;它提供了以下…

何为Jenkins

何为Jenkins Jenkins Jenkins 是一个开源的自动化服务器&#xff0c;广泛用于 持续集成&#xff08;CI&#xff09; 和 持续交付&#xff08;CD&#xff09; 的场景。它可以自动化软件开发中的构建、测试、部署等任务&#xff0c;从而提高开发效率、确保代码质量&#xff0c;…

大数据技术之HBase中的HRegion

如果你正在学习大数据&#xff0c;你应该知道HBase是一个列式存储的NoSQL分布式数据库&#xff0c;可以配合Hadoop来使用。今天自己简单做了几页PPT&#xff0c;解释了一下HBase当中HRegion的基本概念&#xff0c;很多初学者在学习的时候对HRegion这个概念一直懵懵懂懂&#xf…