0


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

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

文章说明

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

核心源码

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

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

接口测试页面

<scriptsetup>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]);}}});functionupdateSelectInterface(){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();functionjudgeInteger(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");return0;}returnparseInt(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);});// 并发控制参数constMAX_CONCURRENT_REQUESTS=5;// 设置最大并发请求数let runningRequests =0;// 当前运行中的请求数const requestQueue =[];// 请求队列// 执行请求functionenqueueRequest(resultGroup, requestItem){
  requestQueue.push({resultGroup, requestItem});processQueue();}// 处理队列functionprocessQueue(){while(runningRequests <MAX_CONCURRENT_REQUESTS&& requestQueue.length >0){const{resultGroup, requestItem}= requestQueue.shift();
    runningRequests++;executeRequest(resultGroup, requestItem).finally(()=>{
      runningRequests--;processQueue();// 完成一个请求后,检查队列并启动下一个请求});}}asyncfunctionexecuteRequest(resultGroup, requestItem){let response;try{
    requestItem.createTime = Date.now();switch(requestItem.method){case"get":
        response =awaitgetRequest(requestItem.url);break;case"post":
        response =awaitpostRequest(requestItem.url, requestItem.body);break;case"put":
        response =awaitputRequest(requestItem.url, requestItem.body);break;case"delete":
        response =awaitdeleteRequest(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();awaitopenRecordLog();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);}}// 时间切片处理functionprocessRequestsInChunks(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});}functiontest(){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-formref="formRef":model="data.form":rules="data.rules"label-position="right"label-width="auto"><el-form-itemlabel="测试接口:"prop="selectInterfaceId"><el-selectv-model="data.form.selectInterfaceId"placeholder="请选择测试接口"@change="updateSelectInterface"><templatev-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-itemv-if="data.method && data.method !== 'get'"label="请求报文:"><el-inputv-model="data.form.body":rows="10"placeholder="请输入请求报文"type="textarea"/></el-form-item><el-form-itemlabel="线程数量:"prop="threadNumber"><el-inputv-model="data.form.threadNumber"placeholder="请输入线程数量"/></el-form-item><el-form-itemlabel="每个线程请求次数:"prop="eachRequestTimesForThread"><el-inputv-model="data.form.eachRequestTimesForThread"placeholder="请输入每个线程请求次数"/></el-form-item><el-form-item><divstyle="width: 100%;display: flex;justify-content: center"><el-buttontype="danger"@click="test">测试</el-button></div></el-form-item></el-form><el-divider/><h3style="margin-bottom: 20px"><span>测试结果展示:</span><el-tagtype="primary"style="margin-right: 10px">请求总数:{{ total }}</el-tag><el-tagtype="success"style="margin-right: 10px">已完成:{{ success }}</el-tag><el-tagtype="info"style="margin-right: 10px">请求开始:{{ data.startTime }}</el-tag><el-tagtype="info"style="margin-right: 10px">请求结束:{{ data.endTime }}</el-tag><el-tagtype="info"style="margin-right: 10px">请求总耗时:{{ data.endTime - data.startTime }}</el-tag><el-tagtype="info"style="margin-right: 10px">平均耗时:{{ ((data.endTime - data.startTime) / success).toFixed(2) }}</el-tag><el-tagtype="info">请求平均耗时:{{ totalAverage }}</el-tag></h3><el-tabsstyle="height: fit-content"type="border-card"><templatev-for="resultGroup in data.resultGroupList":key="resultGroup.groupId"><el-tab-pane:label="resultGroup.groupLabel"lazy><h3style="margin-bottom: 20px"><span>请求结果展示:</span><el-tagtype="primary"style="margin-right: 10px">请求总数:{{ resultGroup.requestList.length }}</el-tag><el-tagtype="success"style="margin-right: 10px">已完成:{{ resultGroup.success }}</el-tag><el-tagtype="info"style="margin-right: 10px">请求总耗时:{{ resultGroup.total }}</el-tag><el-tagtype="info">平均耗时:{{ (resultGroup.total / resultGroup.success).toFixed(2) }}</el-tag></h3><el-collapse><templatev-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><stylelang="scss"scoped></style>

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

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

packagecom.boot.util;importcn.hutool.json.JSONObject;importcn.hutool.json.JSONUtil;importcom.boot.entity.Request;importcom.boot.entity.RequestItem;importcom.boot.entity.Result;importcom.boot.entity.ResultGroup;importlombok.AllArgsConstructor;importlombok.Data;importorg.java_websocket.WebSocket;importjava.util.List;importjava.util.Map;/**
 * @author bbyh
 * @date 2024/11/12 15:03
 */@Data@AllArgsConstructorpublicclassHandleHttpThreadimplementsRunnable{privatefinalRequest request;privatefinalWebSocket webSocket;@Overridepublicvoidrun(){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();newThread(()->{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 =newJSONObject();
                    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请求执行的返回消息

<scriptsetup>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]);}}});functionupdateSelectInterface(){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();functionjudgeInteger(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");return0;}returnparseInt(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 ={};functiontest(){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 =newMyWebSocket(8082,function(event){handleHttp(event);},function(){
        socket.send({
          request:{
            url: data.selectInterface.url,
            method: data.selectInterface.method,
            body: data.form.body,
            resultGroupList:getResultGroupList(),}});});}});}asyncfunctionhandleHttp(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();awaitopenRecordLog();await dbOperation.add([{
      url: data.selectInterface.url,
      method: data.selectInterface.method,
      body: data.form.body,
      response: response,
      create_time: createTime,
      spend: spend,},]);awaitnextTick(()=>{if(success.value === total.value){
      data.testing =false;}});}functiongetResultGroupList(){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-formref="formRef":model="data.form":rules="data.rules"label-position="right"label-width="auto"><el-form-itemlabel="测试接口:"prop="selectInterfaceId"><el-selectv-model="data.form.selectInterfaceId"placeholder="请选择测试接口"@change="updateSelectInterface"><templatev-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-itemv-if="data.method && data.method !== 'get'"label="请求报文:"><el-inputv-model="data.form.body":rows="10"placeholder="请输入请求报文"type="textarea"/></el-form-item><el-form-itemlabel="线程数量:"prop="threadNumber"><el-inputv-model="data.form.threadNumber"placeholder="请输入线程数量"/></el-form-item><el-form-itemlabel="每个线程请求次数:"prop="eachRequestTimesForThread"><el-inputv-model="data.form.eachRequestTimesForThread"placeholder="请输入每个线程请求次数"/></el-form-item><el-form-item><divstyle="width: 100%;display: flex;justify-content: center"><el-buttontype="danger"@click="test">测试</el-button></div></el-form-item></el-form><el-divider/><h3style="margin-bottom: 20px"><span>测试结果展示:</span><el-tagtype="primary"style="margin-right: 10px">请求总数:{{ total }}</el-tag><el-tagtype="success"style="margin-right: 10px">已完成:{{ success }}</el-tag><el-tagtype="info"style="margin-right: 10px">请求开始:{{ data.startTime }}</el-tag><el-tagtype="info"style="margin-right: 10px">请求结束:{{ data.endTime }}</el-tag><el-tagtype="info"style="margin-right: 10px">请求总耗时:{{ data.endTime - data.startTime }}</el-tag><el-tagtype="info"style="margin-right: 10px">平均耗时:{{ ((data.endTime - data.startTime) / success).toFixed(2) }}</el-tag><el-tagtype="info">请求平均耗时:{{ totalAverage }}</el-tag></h3><el-tabsstyle="height: fit-content"type="border-card"><templatev-for="resultGroup in data.resultGroupList":key="resultGroup.groupId"><el-tab-pane:label="resultGroup.groupLabel"lazy><h3style="margin-bottom: 20px"><span>请求结果展示:</span><el-tagtype="primary"style="margin-right: 10px">请求总数:{{ resultGroup.requestList.length }}</el-tag><el-tagtype="success"style="margin-right: 10px">已完成:{{ resultGroup.success }}</el-tag><el-tagtype="info"style="margin-right: 10px">请求总耗时:{{ resultGroup.total }}</el-tag><el-tagtype="info">平均耗时:{{ (resultGroup.total / resultGroup.success).toFixed(2) }}</el-tag></h3><el-collapse><templatev-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><stylelang="scss"scoped></style>

运行截图

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

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

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

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

源码下载

接口性能测试工具


本文转载自: https://blog.csdn.net/bingbingyihao/article/details/143719248
版权归原作者 bingbingyihao 所有, 如有侵权,请联系我们删除。

“接口压力测试、性能测试工具”的评论:

还没有评论