From 99200c7f630f6edfbc7da9dba62328c0de879e22 Mon Sep 17 00:00:00 2001 From: liangdaliang Date: Thu, 20 Feb 2025 15:42:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AA=E7=8E=AF=E3=80=81=E8=BD=AE=E8=AF=A2?= =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test-common/pom.xml | 5 + .../core/domain/model/JmeterRequest.java | 112 +++++ .../com/test/common/utils/JMeterUtil.java | 123 +++-- .../com/test/test/domain/TestCaseResult.java | 2 + .../com/test/test/domain/qo/AssertRule.java | 28 ++ .../test/test/domain/qo/AssertRuleResult.java | 21 + .../test/test/domain/qo/ExtractionRule.java | 28 ++ .../test/domain/qo/ExtractionRuleResult.java | 16 + .../test/test/mapper/TestCaseStepMapper.java | 11 +- .../test/service/ITestCaseLogService.java | 62 +++ .../test/service/ITestCaseResultService.java | 62 +++ .../test/test/service/ITestCaseService.java | 71 +++ .../test/service/ITestCaseStepService.java | 8 + .../service/impl/TestCaseLogServiceImpl.java | 96 ++++ .../impl/TestCaseResultServiceImpl.java | 96 ++++ .../service/impl/TestCaseServiceImpl.java | 460 ++++++++++++++++++ .../mapper/test/TestCaseResultMapper.xml | 111 +++++ .../mapper/test/TestCaseStepMapper.xml | 7 + 18 files changed, 1289 insertions(+), 30 deletions(-) create mode 100644 test-common/src/main/java/com/test/common/core/domain/model/JmeterRequest.java create mode 100644 test-test/src/main/java/com/test/test/domain/qo/AssertRule.java create mode 100644 test-test/src/main/java/com/test/test/domain/qo/AssertRuleResult.java create mode 100644 test-test/src/main/java/com/test/test/domain/qo/ExtractionRule.java create mode 100644 test-test/src/main/java/com/test/test/domain/qo/ExtractionRuleResult.java create mode 100644 test-test/src/main/java/com/test/test/service/ITestCaseLogService.java create mode 100644 test-test/src/main/java/com/test/test/service/ITestCaseResultService.java create mode 100644 test-test/src/main/java/com/test/test/service/ITestCaseService.java create mode 100644 test-test/src/main/java/com/test/test/service/impl/TestCaseLogServiceImpl.java create mode 100644 test-test/src/main/java/com/test/test/service/impl/TestCaseResultServiceImpl.java create mode 100644 test-test/src/main/java/com/test/test/service/impl/TestCaseServiceImpl.java create mode 100644 test-test/src/main/resources/mapper/test/TestCaseResultMapper.xml diff --git a/test-common/pom.xml b/test-common/pom.xml index 281613a..9efeb62 100644 --- a/test-common/pom.xml +++ b/test-common/pom.xml @@ -146,6 +146,11 @@ gson 2.10.1 + + com.jayway.jsonpath + json-path + 2.8.0 + diff --git a/test-common/src/main/java/com/test/common/core/domain/model/JmeterRequest.java b/test-common/src/main/java/com/test/common/core/domain/model/JmeterRequest.java new file mode 100644 index 0000000..61a8a9a --- /dev/null +++ b/test-common/src/main/java/com/test/common/core/domain/model/JmeterRequest.java @@ -0,0 +1,112 @@ +package com.test.common.core.domain.model; + +/** + * @author liangdaliang + * @Description:Jmeter调用参数 + * @date 2025-02-19 10:55 + */ +public class JmeterRequest { + /** + * 用例步骤id + */ + private Long id; + + /** + * 请求完整url + */ + private String url; + + /** + * 请求端口号 + */ + private int port; + + /** + * 请求方式 + */ + private String method; + + /** + * 请求body + */ + private String requestBody; + + /** + * 请求参数,url?后面的参数 + */ + private String requestParams; + + /** + * 请求头Json + */ + private String requestHeader; + + /** + * jmeter安装home路径 + */ + private String jmeterHomePath; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getRequestBody() { + return requestBody; + } + + public void setRequestBody(String requestBody) { + this.requestBody = requestBody; + } + + public String getRequestParams() { + return requestParams; + } + + public void setRequestParams(String requestParams) { + this.requestParams = requestParams; + } + + public String getRequestHeader() { + return requestHeader; + } + + public void setRequestHeader(String requestHeader) { + this.requestHeader = requestHeader; + } + + public String getJmeterHomePath() { + return jmeterHomePath; + } + + public void setJmeterHomePath(String jmeterHomePath) { + this.jmeterHomePath = jmeterHomePath; + } +} diff --git a/test-common/src/main/java/com/test/common/utils/JMeterUtil.java b/test-common/src/main/java/com/test/common/utils/JMeterUtil.java index 5df1bac..8f3ab7d 100644 --- a/test-common/src/main/java/com/test/common/utils/JMeterUtil.java +++ b/test-common/src/main/java/com/test/common/utils/JMeterUtil.java @@ -2,6 +2,9 @@ package com.test.common.utils; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.PathNotFoundException; +import com.test.common.core.domain.model.JmeterRequest; import org.apache.jmeter.config.Arguments; import org.apache.jmeter.config.gui.ArgumentsPanel; import org.apache.jmeter.control.LoopController; @@ -54,16 +57,18 @@ public class JMeterUtil { /** * 生成jmeter测试执行计划并获取执行结果 - * @param id - * @param url - * @param port - * @param method - * @param paramJson - * @param requestHeader - * @param jmeterHomePath + * @param jmeterRequest * @return */ - public static Map getJmeterResult(Long id, String url, int port, String method, String paramJson, String requestHeader, String jmeterHomePath) { + public static Map getJmeterResult(JmeterRequest jmeterRequest) { + Long id = jmeterRequest.getId(); + String url = jmeterRequest.getUrl(); + int port = jmeterRequest.getPort(); + String method = jmeterRequest.getMethod().toUpperCase(); + String requestBody = jmeterRequest.getRequestBody(); + String requestParams = jmeterRequest.getRequestParams(); + String requestHeader = jmeterRequest.getRequestHeader(); + String jmeterHomePath = jmeterRequest.getJmeterHomePath(); Map result = null; try { // 1. 初始化 JMeter @@ -83,13 +88,14 @@ public class JMeterUtil { httpSampler = createPostHttpSampler( url, // 请求地址 port, - paramJson // 请求参数 + requestBody, // 请求体 + requestParams // 查询参数 ); } else { httpSampler = createGetHttpSampler( url, // 请求地址 port, - paramJson // 请求参数 + requestParams // 查询参数 ); } // 5. 创建请求头管理器 @@ -189,11 +195,11 @@ public class JMeterUtil { * 创建get请求方式 * @param urlString * @param port - * @param requestJson + * @param requestParams * @return * @throws MalformedURLException */ - private static HTTPSamplerProxy createGetHttpSampler(String urlString, int port, String requestJson) throws MalformedURLException { + private static HTTPSamplerProxy createGetHttpSampler(String urlString, int port, String requestParams) throws MalformedURLException { URL url = new URL(urlString); // 提取域名(不包括端口号) String domain = url.getHost(); @@ -204,6 +210,9 @@ public class JMeterUtil { httpSampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName()); httpSampler.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName()); httpSampler.setDomain(domain); // 提取域名 + if (!StringUtils.isEmpty(requestParams)) { + path = path + "?" + requestParams; + } httpSampler.setPath(path); // 提取路径 httpSampler.setPort(port); httpSampler.setMethod("GET"); @@ -211,17 +220,17 @@ public class JMeterUtil { httpSampler.setUseKeepAlive(true); // 添加请求参数 - Map parameters = new HashMap<>(); - if (!StringUtils.isEmpty(requestJson)) { - Gson gson = new Gson(); - // 定义 Map 的类型 - Type type = new TypeToken>() {}.getType(); - // 将 JSON 字符串转换为 Map - parameters = gson.fromJson(requestJson, type); - path += "?"; - path += mapToQueryString(parameters); - httpSampler.setPath(path); // 提取路径 - } +// Map parameters = new HashMap<>(); +// if (!StringUtils.isEmpty(requestJson)) { +// Gson gson = new Gson(); +// // 定义 Map 的类型 +// Type type = new TypeToken>() {}.getType(); +// // 将 JSON 字符串转换为 Map +// parameters = gson.fromJson(requestJson, type); +// path += "?"; +// path += mapToQueryString(parameters); +// httpSampler.setPath(path); // 提取路径 +// } return httpSampler; } @@ -249,11 +258,12 @@ public class JMeterUtil { * 创建post请求方式 * @param urlString * @param port - * @param requestJson + * @param requestBody + * @param requestParams * @return * @throws MalformedURLException */ - private static HTTPSamplerProxy createPostHttpSampler(String urlString, int port, String requestJson) throws MalformedURLException { + private static HTTPSamplerProxy createPostHttpSampler(String urlString, int port, String requestBody, String requestParams) throws MalformedURLException { URL url = new URL(urlString); // 提取域名(不包括端口号) String domain = url.getHost(); @@ -265,15 +275,18 @@ public class JMeterUtil { httpSampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName()); httpSampler.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName()); httpSampler.setDomain(domain); + if (!StringUtils.isEmpty(requestParams)) { + path = path + "?" + requestParams; + } httpSampler.setPath(path); httpSampler.setPort(port); httpSampler.setMethod("POST"); httpSampler.setConnectTimeout("5000"); httpSampler.setUseKeepAlive(true); - // 设置请求头为 JSON + // 设置请求体 httpSampler.setPostBodyRaw(true); // 使用原始请求体 - httpSampler.addNonEncodedArgument("", requestJson, ""); + httpSampler.addNonEncodedArgument("", requestBody, ""); return httpSampler; } @@ -398,12 +411,21 @@ public class JMeterUtil { Map resultMap = new HashMap<>(); Element httpSampleElement = (Element) httpSampleNode; String costMiliseconds = httpSampleElement.getAttribute("t"); + String responseCode = httpSampleElement.getAttribute("rc"); + resultMap.put("responseCode", responseCode); resultMap.put("costMiliseconds", costMiliseconds); // 提取子节点的内容 NodeList responseDataList = httpSampleElement.getElementsByTagName("responseData"); if (responseDataList.getLength() > 0) { Element responseDataElement = (Element) responseDataList.item(0); - resultMap.put("costMiliseconds", responseDataElement.getTextContent()); + resultMap.put("responseBody", responseDataElement.getTextContent()); + return resultMap; + } + // 提取子节点的内容 + NodeList responseHeaderList = httpSampleElement.getElementsByTagName("responseHeader"); + if (responseHeaderList.getLength() > 0) { + Element responseHeaderElement = (Element) responseHeaderList.item(0); + resultMap.put("responseHeader", responseHeaderElement.getTextContent()); return resultMap; } } @@ -413,13 +435,56 @@ public class JMeterUtil { return null; } + /** + * 从响应头中读取Map信息结构 + * @param responseHeader + * @return + */ + public static Map parseHeaders(String responseHeader) { + Map headerMap = new HashMap<>(); + // 按行拆分 + String[] lines = responseHeader.split("\n"); + // 遍历每一行 + for (String line : lines) { + // 忽略空行和 HTTP 状态行 + if (line.trim().isEmpty() || line.startsWith("HTTP/")) { + continue; + } + // 按冒号拆分键值对 + int colonIndex = line.indexOf(":"); + if (colonIndex > 0) { + String key = line.substring(0, colonIndex).trim(); + String value = line.substring(colonIndex + 1).trim(); + headerMap.put(key, value); + } + } + return headerMap; + } + + /** + * 根据 JSONPath 表达式从 JSON 中提取字段值 + * + * @param json JSON 字符串 + * @param expression JSONPath 表达式 + * @return 字段值,如果字段不存在则返回 null + */ + public static String extractFieldValue(String json, String expression) { + try { + Object result = JsonPath.read(json, expression); + return result != null ? result.toString() : null; + } catch (PathNotFoundException e) { + // 如果字段不存在,返回 null + return null; + } + } + public static void main(String[] args) { String paramJson = "{\"md5Value\":\"value1\",\"fileName\":\"value2\"}"; paramJson = "{\"name\":\"John\",\"city\":\"New York\",\"age\":30}"; String requestHeader = "{\"Content-Type\":\"application/json; charset=UTF-8\",\"Authorization\":\"Bearer token123\"}"; String jmeterHomePath = "D:/apache-jmeter-5.4.3"; // String result = getJmeterResult("http://127.0.0.1:3000/api/items/add", 3000, "POST", paramJson, requestHeader, jmeterHomePath); - getJmeterResult(123L, "http://127.0.0.1:3000/api/items/user", 3000, "GET", paramJson, requestHeader, jmeterHomePath); +// getJmeterResult(123L, "http://127.0.0.1:3000/api/items/user", 3000, "GET", paramJson, requestHeader, jmeterHomePath); // System.out.println(result); } } diff --git a/test-test/src/main/java/com/test/test/domain/TestCaseResult.java b/test-test/src/main/java/com/test/test/domain/TestCaseResult.java index d364e7f..837245a 100644 --- a/test-test/src/main/java/com/test/test/domain/TestCaseResult.java +++ b/test-test/src/main/java/com/test/test/domain/TestCaseResult.java @@ -39,6 +39,8 @@ public class TestCaseResult extends BaseEntity { /** http 响应体 */ @Excel(name = "http 响应体") private String responseBody; + + /** sql结果 */ private String sqlResult; /** 循环次 */ diff --git a/test-test/src/main/java/com/test/test/domain/qo/AssertRule.java b/test-test/src/main/java/com/test/test/domain/qo/AssertRule.java new file mode 100644 index 0000000..6630b91 --- /dev/null +++ b/test-test/src/main/java/com/test/test/domain/qo/AssertRule.java @@ -0,0 +1,28 @@ +package com.test.test.domain.qo; + +import lombok.Data; + +/** + * @author liangdaliang + * @Description:校验规则 + * @date 2025-02-19 17:33 + */ +@Data +public class AssertRule { + /** + * 描述 + */ + private String name; + /** + * 对象 + */ + private String source; + /** + * 条件 + */ + private String fn; + /** + * 比较值 + */ + private String target; +} diff --git a/test-test/src/main/java/com/test/test/domain/qo/AssertRuleResult.java b/test-test/src/main/java/com/test/test/domain/qo/AssertRuleResult.java new file mode 100644 index 0000000..bac78b9 --- /dev/null +++ b/test-test/src/main/java/com/test/test/domain/qo/AssertRuleResult.java @@ -0,0 +1,21 @@ +package com.test.test.domain.qo; + +import lombok.Data; + +/** + * @author liangdaliang + * @Description: + * @date 2025-02-19 21:02 + */ +@Data +public class AssertRuleResult extends AssertRule { + /** + * 实际值 + */ + private String value; + + /** + * 结果:通过;未通过 + */ + private String result; +} diff --git a/test-test/src/main/java/com/test/test/domain/qo/ExtractionRule.java b/test-test/src/main/java/com/test/test/domain/qo/ExtractionRule.java new file mode 100644 index 0000000..1d994e0 --- /dev/null +++ b/test-test/src/main/java/com/test/test/domain/qo/ExtractionRule.java @@ -0,0 +1,28 @@ +package com.test.test.domain.qo; + +import lombok.Data; + +/** + * @author liangdaliang + * @Description:提取规则 + * @date 2025-02-19 16:04 + */ +@Data +public class ExtractionRule { + /** + * 变量名 + */ + private String name; + /** + * 提取方式 + */ + private String type; + /** + * 提取对象 + */ + private String content; + /** + * 提取表达式 + */ + private String path; +} diff --git a/test-test/src/main/java/com/test/test/domain/qo/ExtractionRuleResult.java b/test-test/src/main/java/com/test/test/domain/qo/ExtractionRuleResult.java new file mode 100644 index 0000000..4471b3c --- /dev/null +++ b/test-test/src/main/java/com/test/test/domain/qo/ExtractionRuleResult.java @@ -0,0 +1,16 @@ +package com.test.test.domain.qo; + +import lombok.Data; + +/** + * @author liangdaliang + * @Description:提取规则结果 + * @date 2025-02-19 16:12 + */ +@Data +public class ExtractionRuleResult extends ExtractionRule { + /** + * 提取结果 + */ + private String value; +} diff --git a/test-test/src/main/java/com/test/test/mapper/TestCaseStepMapper.java b/test-test/src/main/java/com/test/test/mapper/TestCaseStepMapper.java index 44525c7..1c0079d 100644 --- a/test-test/src/main/java/com/test/test/mapper/TestCaseStepMapper.java +++ b/test-test/src/main/java/com/test/test/mapper/TestCaseStepMapper.java @@ -1,8 +1,9 @@ package com.test.test.mapper; -import java.util.List; import com.test.test.domain.TestCaseStep; +import java.util.List; + /** * 用例步骤Mapper接口 * @@ -58,4 +59,12 @@ public interface TestCaseStepMapper * @return 结果 */ public int deleteTestCaseStepByIds(Long[] ids); + + /** + * 根据用例id查询用例步骤列表并按照步骤顺序排序 + * + * @param caseId 用例id + * @return 用例步骤集合 + */ + public List selectTestCaseStepListByCaseId(Long caseId); } diff --git a/test-test/src/main/java/com/test/test/service/ITestCaseLogService.java b/test-test/src/main/java/com/test/test/service/ITestCaseLogService.java new file mode 100644 index 0000000..9e17a36 --- /dev/null +++ b/test-test/src/main/java/com/test/test/service/ITestCaseLogService.java @@ -0,0 +1,62 @@ +package com.test.test.service; + +import com.test.test.domain.TestCaseLog; + +import java.util.List; + +/** + * 用例日志Service接口 + * + * @author liangdl + * @date 2025-02-18 + */ +public interface ITestCaseLogService +{ + /** + * 查询用例日志 + * + * @param id 用例日志主键 + * @return 用例日志 + */ + public TestCaseLog selectTestCaseLogById(Long id); + + /** + * 查询用例日志列表 + * + * @param testCaseLog 用例日志 + * @return 用例日志集合 + */ + public List selectTestCaseLogList(TestCaseLog testCaseLog); + + /** + * 新增用例日志 + * + * @param testCaseLog 用例日志 + * @return 结果 + */ + public int insertTestCaseLog(TestCaseLog testCaseLog); + + /** + * 修改用例日志 + * + * @param testCaseLog 用例日志 + * @return 结果 + */ + public int updateTestCaseLog(TestCaseLog testCaseLog); + + /** + * 批量删除用例日志 + * + * @param ids 需要删除的用例日志主键集合 + * @return 结果 + */ + public int deleteTestCaseLogByIds(Long[] ids); + + /** + * 删除用例日志信息 + * + * @param id 用例日志主键 + * @return 结果 + */ + public int deleteTestCaseLogById(Long id); +} diff --git a/test-test/src/main/java/com/test/test/service/ITestCaseResultService.java b/test-test/src/main/java/com/test/test/service/ITestCaseResultService.java new file mode 100644 index 0000000..59e7fff --- /dev/null +++ b/test-test/src/main/java/com/test/test/service/ITestCaseResultService.java @@ -0,0 +1,62 @@ +package com.test.test.service; + +import com.test.test.domain.TestCaseResult; + +import java.util.List; + +/** + * 用例执行报告Service接口 + * + * @author liangdl + * @date 2025-02-18 + */ +public interface ITestCaseResultService +{ + /** + * 查询用例执行报告 + * + * @param id 用例执行报告主键 + * @return 用例执行报告 + */ + public TestCaseResult selectTestCaseResultById(Long id); + + /** + * 查询用例执行报告列表 + * + * @param testCaseResult 用例执行报告 + * @return 用例执行报告集合 + */ + public List selectTestCaseResultList(TestCaseResult testCaseResult); + + /** + * 新增用例执行报告 + * + * @param testCaseResult 用例执行报告 + * @return 结果 + */ + public int insertTestCaseResult(TestCaseResult testCaseResult); + + /** + * 修改用例执行报告 + * + * @param testCaseResult 用例执行报告 + * @return 结果 + */ + public int updateTestCaseResult(TestCaseResult testCaseResult); + + /** + * 批量删除用例执行报告 + * + * @param ids 需要删除的用例执行报告主键集合 + * @return 结果 + */ + public int deleteTestCaseResultByIds(Long[] ids); + + /** + * 删除用例执行报告信息 + * + * @param id 用例执行报告主键 + * @return 结果 + */ + public int deleteTestCaseResultById(Long id); +} diff --git a/test-test/src/main/java/com/test/test/service/ITestCaseService.java b/test-test/src/main/java/com/test/test/service/ITestCaseService.java new file mode 100644 index 0000000..89c3b13 --- /dev/null +++ b/test-test/src/main/java/com/test/test/service/ITestCaseService.java @@ -0,0 +1,71 @@ +package com.test.test.service; + +import com.test.test.domain.TestCase; + +import java.util.List; + +/** + * 用例Service接口 + * + * @author liangdl + * @date 2025-02-18 + */ +public interface ITestCaseService +{ + /** + * 查询用例 + * + * @param id 用例主键 + * @return 用例 + */ + public TestCase selectTestCaseById(Long id); + + /** + * 查询用例列表 + * + * @param testCase 用例 + * @return 用例集合 + */ + public List selectTestCaseList(TestCase testCase); + + /** + * 新增用例 + * + * @param testCase 用例 + * @return 结果 + */ + public int insertTestCase(TestCase testCase); + + /** + * 修改用例 + * + * @param testCase 用例 + * @return 结果 + */ + public int updateTestCase(TestCase testCase); + + /** + * 批量删除用例 + * + * @param ids 需要删除的用例主键集合 + * @return 结果 + */ + public int deleteTestCaseByIds(Long[] ids); + + /** + * 删除用例信息 + * + * @param id 用例主键 + * @return 结果 + */ + public int deleteTestCaseById(Long id); + + /** + * 执行用例计划 + * + * @param id 用例主键id + * @param jmeterHomePath jmeter安装路径 + * @return 是否成功 + */ + public boolean executeTestCaseById(Long id, String jmeterHomePath); +} diff --git a/test-test/src/main/java/com/test/test/service/ITestCaseStepService.java b/test-test/src/main/java/com/test/test/service/ITestCaseStepService.java index b84812b..8891af5 100644 --- a/test-test/src/main/java/com/test/test/service/ITestCaseStepService.java +++ b/test-test/src/main/java/com/test/test/service/ITestCaseStepService.java @@ -61,6 +61,14 @@ public interface ITestCaseStepService */ public int deleteTestCaseStepById(Long id); + /** + * 根据用例id查询用例步骤列表并按照步骤顺序排序 + * + * @param caseId 用例id + * @return 用例步骤集合 + */ + public List selectTestCaseStepListByCaseId(Long caseId); + /** * 执行jmeter用例步骤计划 * diff --git a/test-test/src/main/java/com/test/test/service/impl/TestCaseLogServiceImpl.java b/test-test/src/main/java/com/test/test/service/impl/TestCaseLogServiceImpl.java new file mode 100644 index 0000000..6cb5442 --- /dev/null +++ b/test-test/src/main/java/com/test/test/service/impl/TestCaseLogServiceImpl.java @@ -0,0 +1,96 @@ +package com.test.test.service.impl; + +import com.test.test.domain.TestCaseLog; +import com.test.test.mapper.TestCaseLogMapper; +import com.test.test.service.ITestCaseLogService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 用例日志Service业务层处理 + * + * @author liangdl + * @date 2025-02-18 + */ +@Slf4j +@Service +public class TestCaseLogServiceImpl implements ITestCaseLogService +{ + @Resource + private TestCaseLogMapper testCaseLogMapper; + + /** + * 查询用例日志 + * + * @param id 用例日志主键 + * @return 用例日志 + */ + @Override + public TestCaseLog selectTestCaseLogById(Long id) + { + return testCaseLogMapper.selectTestCaseLogById(id); + } + + /** + * 查询用例日志列表 + * + * @param testCaseLog 用例日志 + * @return 用例日志 + */ + @Override + public List selectTestCaseLogList(TestCaseLog testCaseLog) + { + return testCaseLogMapper.selectTestCaseLogList(testCaseLog); + } + + /** + * 新增用例日志 + * + * @param testCaseLog 用例日志 + * @return 结果 + */ + @Override + public int insertTestCaseLog(TestCaseLog testCaseLog) + { + return testCaseLogMapper.insertTestCaseLog(testCaseLog); + } + + /** + * 修改用例日志 + * + * @param testCaseLog 用例日志 + * @return 结果 + */ + @Override + public int updateTestCaseLog(TestCaseLog testCaseLog) + { + return testCaseLogMapper.updateTestCaseLog(testCaseLog); + } + + /** + * 批量删除用例日志 + * + * @param ids 需要删除的用例日志主键 + * @return 结果 + */ + @Override + public int deleteTestCaseLogByIds(Long[] ids) + { + return testCaseLogMapper.deleteTestCaseLogByIds(ids); + } + + /** + * 删除用例日志信息 + * + * @param id 用例日志主键 + * @return 结果 + */ + @Override + public int deleteTestCaseLogById(Long id) + { + return testCaseLogMapper.deleteTestCaseLogById(id); + } +} diff --git a/test-test/src/main/java/com/test/test/service/impl/TestCaseResultServiceImpl.java b/test-test/src/main/java/com/test/test/service/impl/TestCaseResultServiceImpl.java new file mode 100644 index 0000000..9ccf867 --- /dev/null +++ b/test-test/src/main/java/com/test/test/service/impl/TestCaseResultServiceImpl.java @@ -0,0 +1,96 @@ +package com.test.test.service.impl; + +import com.test.test.domain.TestCaseResult; +import com.test.test.mapper.TestCaseResultMapper; +import com.test.test.service.ITestCaseResultService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 用例执行报告Service业务层处理 + * + * @author liangdl + * @date 2025-02-18 + */ +@Slf4j +@Service +public class TestCaseResultServiceImpl implements ITestCaseResultService +{ + @Resource + private TestCaseResultMapper testCaseResultMapper; + + /** + * 查询用例执行报告 + * + * @param id 用例执行报告主键 + * @return 用例执行报告 + */ + @Override + public TestCaseResult selectTestCaseResultById(Long id) + { + return testCaseResultMapper.selectTestCaseResultById(id); + } + + /** + * 查询用例执行报告列表 + * + * @param testCaseResult 用例执行报告 + * @return 用例执行报告 + */ + @Override + public List selectTestCaseResultList(TestCaseResult testCaseResult) + { + return testCaseResultMapper.selectTestCaseResultList(testCaseResult); + } + + /** + * 新增用例执行报告 + * + * @param testCaseResult 用例执行报告 + * @return 结果 + */ + @Override + public int insertTestCaseResult(TestCaseResult testCaseResult) + { + return testCaseResultMapper.insertTestCaseResult(testCaseResult); + } + + /** + * 修改用例执行报告 + * + * @param testCaseResult 用例执行报告 + * @return 结果 + */ + @Override + public int updateTestCaseResult(TestCaseResult testCaseResult) + { + return testCaseResultMapper.updateTestCaseResult(testCaseResult); + } + + /** + * 批量删除用例执行报告 + * + * @param ids 需要删除的用例执行报告主键 + * @return 结果 + */ + @Override + public int deleteTestCaseResultByIds(Long[] ids) + { + return testCaseResultMapper.deleteTestCaseResultByIds(ids); + } + + /** + * 删除用例执行报告信息 + * + * @param id 用例执行报告主键 + * @return 结果 + */ + @Override + public int deleteTestCaseResultById(Long id) + { + return testCaseResultMapper.deleteTestCaseResultById(id); + } +} diff --git a/test-test/src/main/java/com/test/test/service/impl/TestCaseServiceImpl.java b/test-test/src/main/java/com/test/test/service/impl/TestCaseServiceImpl.java new file mode 100644 index 0000000..81fdf64 --- /dev/null +++ b/test-test/src/main/java/com/test/test/service/impl/TestCaseServiceImpl.java @@ -0,0 +1,460 @@ +package com.test.test.service.impl; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.test.common.utils.DateUtils; +import com.test.common.utils.JMeterUtil; +import com.test.common.utils.StringUtils; +import com.test.test.domain.TestCase; +import com.test.test.domain.TestCaseResult; +import com.test.test.domain.TestCaseStep; +import com.test.test.domain.qo.AssertRule; +import com.test.test.domain.qo.AssertRuleResult; +import com.test.test.domain.qo.ExtractionRule; +import com.test.test.domain.qo.ExtractionRuleResult; +import com.test.test.mapper.TestCaseMapper; +import com.test.test.mapper.TestCaseResultMapper; +import com.test.test.service.ITestCaseService; +import com.test.test.service.ITestCaseStepService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 用例Service业务层处理 + * + * @author liangdl + * @date 2025-02-18 + */ +@Slf4j +@Service +public class TestCaseServiceImpl implements ITestCaseService +{ + @Resource + private TestCaseMapper testCaseMapper; + @Resource + private TestCaseResultMapper testCaseResultMapper; + + @Autowired + private ITestCaseStepService testCaseStepService; + + /** + * 查询用例 + * + * @param id 用例主键 + * @return 用例 + */ + @Override + public TestCase selectTestCaseById(Long id) + { + return testCaseMapper.selectTestCaseById(id); + } + + /** + * 查询用例列表 + * + * @param testCase 用例 + * @return 用例 + */ + @Override + public List selectTestCaseList(TestCase testCase) + { + return testCaseMapper.selectTestCaseList(testCase); + } + + /** + * 新增用例 + * + * @param testCase 用例 + * @return 结果 + */ + @Override + public int insertTestCase(TestCase testCase) + { + testCase.setCreateTime(DateUtils.getNowDate()); + return testCaseMapper.insertTestCase(testCase); + } + + /** + * 修改用例 + * + * @param testCase 用例 + * @return 结果 + */ + @Override + public int updateTestCase(TestCase testCase) + { + testCase.setUpdateTime(DateUtils.getNowDate()); + return testCaseMapper.updateTestCase(testCase); + } + + /** + * 批量删除用例 + * + * @param ids 需要删除的用例主键 + * @return 结果 + */ + @Override + public int deleteTestCaseByIds(Long[] ids) + { + return testCaseMapper.deleteTestCaseByIds(ids); + } + + /** + * 删除用例信息 + * + * @param id 用例主键 + * @return 结果 + */ + @Override + public int deleteTestCaseById(Long id) + { + return testCaseMapper.deleteTestCaseById(id); + } + + /** + * 执行用例计划 + * + * @param id 用例主键id + * @param jmeterHomePath jmeter安装路径 + * @return 是否成功 + */ + @Override + public boolean executeTestCaseById(Long id, String jmeterHomePath) { + TestCase testCase = this.selectTestCaseById(id); + if (testCase == null || "2".equals(testCase.getDelFlag())) { + log.error("用例计划已被删除,不能执行!"); + return false; + } + List testCaseStepList = testCaseStepService.selectTestCaseStepListByCaseId(id); + for (TestCaseStep testCaseStep : testCaseStepList) { + if (testCaseStep.getType() == 1L) { + // http接口处理 + boolean httpTestResult = doHttpRequestTest(testCase, testCaseStep, jmeterHomePath); + if (!httpTestResult) { + log.error("用例步骤:{}执行失败!", testCaseStep.getName()); + return false; + } + } else if (testCaseStep.getType() == 2L) { + // 数据库接口处理 + } else if (testCaseStep.getType() == 3L) { + // 循环节点处理 + boolean pollTestResult = doPollRequestTest(testCase, testCaseStep, jmeterHomePath); + if (!pollTestResult) { + log.error("循环用例步骤:{}执行失败!", testCaseStep.getName()); + return false; + } + } else if (testCaseStep.getType() == 4L) { + // 轮询节点处理 + boolean loopTestResult = doLoopRequestTest(testCase, testCaseStep, jmeterHomePath); + if (!loopTestResult) { + log.error("轮询用例步骤:{}执行失败!", testCaseStep.getName()); + return false; + } + } else { + log.error("错误的用例步骤类型!"); + } + } + return true; + } + + /** + * 处理http接口测试 + * @param testCase + * @param testCaseStep + * @param jmeterHomePath + * @return 成功或失败 + */ + private boolean doHttpRequestTest(TestCase testCase, TestCaseStep testCaseStep, String jmeterHomePath) { + try { + String assignment = testCaseStep.getAssignment(); // 参数提取 + String assertion = testCaseStep.getAssertion(); // 校验规则 + TestCaseResult testCaseResult = new TestCaseResult(); + testCaseResult.setExecuteTime(DateUtils.getNowDate()); + Map resultMap = testCaseStepService.executeJmeterTestCaseStepById(testCaseStep.getId(), jmeterHomePath); + testCaseResult.setCaseId(testCase.getId()); + testCaseResult.setStepId(testCaseStep.getId()); + if (resultMap != null) { + testCaseResult.setResponseHeader(resultMap.get("responseHeader")); + testCaseResult.setResponseBody(resultMap.get("responseBody")); + // 处理参数提取 + Map assignmentResultMap = new HashMap<>(); + String assignmentResult = this.dealTestCaseStepAssignment(resultMap, assignmentResultMap, assignment); + testCaseResult.setAssignment(assignmentResult); + // 根据提取结果处理校验规则 + String assertionResult = this.dealTestCaseStepAssertion(assignmentResultMap, assertion); + testCaseResult.setAssertion(assertionResult); + Long useTime = null; + if (resultMap.get("useTime") != null) { + useTime = Long.valueOf(resultMap.get("useTime")); + } + testCaseResult.setUseTime(useTime); + if ("fail".equals(assignmentResultMap.get("assertionResult"))) { + testCaseResult.setStatus("失败"); + testCaseResultMapper.insertTestCaseResult(testCaseResult); + return false; + } else { + testCaseResult.setStatus("成功"); + testCaseResultMapper.insertTestCaseResult(testCaseResult); + } + } else { + testCaseResult.setAssignment(assignment); + testCaseResult.setAssertion(assertion); + testCaseResult.setUseTime(0L); + testCaseResult.setStatus("失败"); + testCaseResultMapper.insertTestCaseResult(testCaseResult); + return false; + } + } catch (Exception e) { + log.error("HTTP请求测试发生异常: ", e); + return false; + } + return true; + } + + /** + * 处理循环节点接口测试 + * @param testCase + * @param testCaseStep + * @param jmeterHomePath + * @return 是否成功 + */ + private boolean doPollRequestTest(TestCase testCase, TestCaseStep testCaseStep, String jmeterHomePath) { + try { + // 先查出所有子节点测试步骤 + long pollCountCount = testCaseStep.getCount(); + Integer async = testCaseStep.getAsync(); + TestCaseResult testCaseResult = new TestCaseResult(); + testCaseResult.setExecuteTime(DateUtils.getNowDate()); + testCaseResult.setCaseId(testCase.getId()); + testCaseResult.setStepId(testCaseStep.getId()); + Long parentId = testCaseStep.getId(); + TestCaseStep testCaseStepInput = new TestCaseStep(); + testCaseStepInput.setCaseId(testCase.getId()); + testCaseStepInput.setParentId(parentId); + testCaseStepInput.setDelFlag("0"); + List testCaseStepChildList = testCaseStepService.selectTestCaseStepList(testCaseStepInput); + long realPollCount = 1; // 实际循环次数 + boolean pollResult = true; + long startTime = System.currentTimeMillis(); + for (int i = 0; i < pollCountCount; i++) { + for (TestCaseStep testCaseStepChild : testCaseStepChildList) { + if (testCaseStepChild.getType() == 1L) { + // http接口处理 + boolean httpTestResult = doHttpRequestTest(testCase, testCaseStepChild, jmeterHomePath); + if (!httpTestResult) { + log.error("子用例步骤:{}执行失败!", testCaseStepChild.getName()); + pollResult = false; + break; + } + } else if (testCaseStepChild.getType() == 2L) { + // 数据库接口处理 + } + } + if (!pollResult) { + break; + } + realPollCount++; + } + testCaseResult.setUseTime(System.currentTimeMillis() - startTime); + if (pollResult) { + testCaseResult.setStatus("成功"); + testCaseResult.setPollingCount(realPollCount); + testCaseResultMapper.insertTestCaseResult(testCaseResult); + return true; + } else { + testCaseResult.setStatus("失败"); + testCaseResult.setPollingCount(realPollCount); + testCaseResultMapper.insertTestCaseResult(testCaseResult); + return false; + } + } catch (Exception e) { + log.error("循环节点测试发生异常: ", e); + return false; + } + } + + /** + * 处理轮询节点接口测试 + * @param testCase + * @param testCaseStep + * @param jmeterHomePath + * @return 是否成功 + */ + private boolean doLoopRequestTest(TestCase testCase, TestCaseStep testCaseStep, String jmeterHomePath) { + try { + // 先查出所有子节点测试步骤 + String breakError = testCaseStep.getBreakError(); + long interval = testCaseStep.getInterval() == null ? 0 : testCaseStep.getInterval();; + long loopCountCount = testCaseStep.getCount(); + TestCaseResult testCaseResult = new TestCaseResult(); + testCaseResult.setExecuteTime(DateUtils.getNowDate()); + testCaseResult.setCaseId(testCase.getId()); + testCaseResult.setStepId(testCaseStep.getId()); + Long parentId = testCaseStep.getId(); + TestCaseStep testCaseStepInput = new TestCaseStep(); + testCaseStepInput.setCaseId(testCase.getId()); + testCaseStepInput.setParentId(parentId); + testCaseStepInput.setDelFlag("0"); + List testCaseStepChildList = testCaseStepService.selectTestCaseStepList(testCaseStepInput); + long realLoopCount = 1; // 实际轮询次数 + boolean loopResult = false; + long startTime = System.currentTimeMillis(); + int maxSize = testCaseStepChildList.size(); + for (int i = 0; i < loopCountCount; i++) { + if (i > 0) { + try { + Thread.sleep(interval); + } catch (InterruptedException e) { + log.error("线程睡眠被中断: ", e); + } + } + int index = 0; + for (TestCaseStep testCaseStepChild : testCaseStepChildList) { + if (testCaseStepChild.getType() == 1L) { + // http接口处理 + boolean httpTestResult = doHttpRequestTest(testCase, testCaseStepChild, jmeterHomePath); + if (!httpTestResult) { + log.error("子用例步骤:{}执行失败!", testCaseStepChild.getName()); + break; + } + } else if (testCaseStepChild.getType() == 2L) { + // 数据库接口处理 + } + index ++; + } + realLoopCount ++; + if (maxSize == index) { + loopResult = true; + break; + } + } + testCaseResult.setUseTime(System.currentTimeMillis() - startTime); + if (loopResult) { + testCaseResult.setStatus("成功"); + testCaseResult.setLoopCount(realLoopCount); + testCaseResultMapper.insertTestCaseResult(testCaseResult); + return true; + } else { + testCaseResult.setStatus("失败"); + testCaseResult.setLoopCount(realLoopCount); + testCaseResultMapper.insertTestCaseResult(testCaseResult); + return false; + } + } catch (Exception e) { + log.error("轮询节点测试发生异常: ", e); + return false; + } + } + + /** + * 处理响应字段提取 + * @param resultMap + * @param assignmentResultMap + * @param assignment + * @return 处理后的提取结果 + */ + private String dealTestCaseStepAssignment(Map resultMap, Map assignmentResultMap, String assignment) { + String responseHeader = resultMap.get("responseHeader"); + String responseBody = resultMap.get("responseBody"); + String responseCode = resultMap.get("responseCode"); + if (!StringUtils.isEmpty(assignment) && !"[]".equals(assignment)) { + Map responseHeaderMap = JMeterUtil.parseHeaders(responseHeader); + Gson gson = new Gson(); + Type ruleListType = new TypeToken>() {}.getType(); + // 将 JSON 字符串解析为 List + List rules = gson.fromJson(assignment, ruleListType); + List ruleResultList = new ArrayList<>(); + for (ExtractionRule rule : rules) { + ExtractionRuleResult ruleResult = new ExtractionRuleResult(); + BeanUtils.copyProperties(rule, ruleResult); + String type = rule.getType(); + String content = rule.getContent(); + String path = rule.getPath(); + if ("${RESPONSE_BODY}".equals(content)) { + if (type.startsWith("JSON")) { + if (!path.startsWith("$.")) { + path = "$." + path; + } + String value = JMeterUtil.extractFieldValue(responseBody, path); + ruleResult.setValue(value); + } + } else if ("${RESPONSE_HEADER}".equals(content)) { + String value = responseHeaderMap.get(path); + ruleResult.setValue(value); + } else if ("${RESPONSE_CODE}".equals(content)) { + ruleResult.setValue(responseCode); + } + assignmentResultMap.put(rule.getName(), ruleResult.getValue()); + ruleResultList.add(ruleResult); + } + return gson.toJson(ruleResultList); + } else { + return assignment; + } + } + + /** + * 校验测试用例步骤是否通过 + * @param assignmentResultMap + * @param assertion + * @return + */ + private String dealTestCaseStepAssertion(Map assignmentResultMap, String assertion) { + if (!StringUtils.isEmpty(assertion) && !"[]".equals(assertion)) { + Gson gson = new Gson(); + Type ruleListType = new TypeToken>() {}.getType(); + // 将 JSON 字符串解析为 List + List rules = gson.fromJson(assertion, ruleListType); + List ruleResultList = new ArrayList<>(); + for (AssertRule rule : rules) { + AssertRuleResult ruleResult = new AssertRuleResult(); + BeanUtils.copyProperties(rule, ruleResult); + String source = rule.getSource(); + String target = rule.getTarget(); + String fn = rule.getFn(); + String value = assignmentResultMap.get(source); + ruleResult.setValue(value); + if ("等于".equals(fn)) { + if (target.equals(value)) { + ruleResult.setResult("通过"); + } else { + ruleResult.setResult("未通过"); + } + } else if ("大于".equals(fn)) { + int result = value.compareTo(target); + if (result > 0) { + ruleResult.setResult("通过"); + } else { + ruleResult.setResult("未通过"); + } + } else if ("小于".equals(fn)) { + int result = value.compareTo(target); + if (result < 0) { + ruleResult.setResult("通过"); + } else { + ruleResult.setResult("未通过"); + } + } else { + ruleResult.setResult("未通过"); + } + if ("未通过".equals(ruleResult.getResult())) { + assignmentResultMap.put("assertionResult", "fail"); + } + ruleResultList.add(ruleResult); + } + return gson.toJson(ruleResultList); + } else { + return assertion; + } + + } +} diff --git a/test-test/src/main/resources/mapper/test/TestCaseResultMapper.xml b/test-test/src/main/resources/mapper/test/TestCaseResultMapper.xml new file mode 100644 index 0000000..8e5950a --- /dev/null +++ b/test-test/src/main/resources/mapper/test/TestCaseResultMapper.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + select id, case_id, step_id, response_header, response_body, sql_result, polling_count, loop_count, assignment, assertion, use_time, execute_time, status from test_case_result + + + + + + + + insert into test_case_result + + case_id, + step_id, + response_header, + response_body, + sql_result, + polling_count, + loop_count, + assignment, + assertion, + use_time, + execute_time, + status, + + + #{caseId}, + #{stepId}, + #{responseHeader}, + #{responseBody}, + #{sqlResult}, + #{pollingCount}, + #{loopCount}, + #{assignment}, + #{assertion}, + #{useTime}, + #{executeTime}, + #{status}, + + + + + update test_case_result + + case_id = #{caseId}, + step_id = #{stepId}, + response_header = #{responseHeader}, + response_body = #{responseBody}, + sql_result = #{sqlResult}, + polling_count = #{pollingCount}, + loop_count = #{loopCount}, + assignment = #{assignment}, + assertion = #{assertion}, + use_time = #{useTime}, + execute_time = #{executeTime}, + status = #{status}, + + where id = #{id} + + + + delete from test_case_result where id = #{id} + + + + delete from test_case_result where id in + + #{id} + + + diff --git a/test-test/src/main/resources/mapper/test/TestCaseStepMapper.xml b/test-test/src/main/resources/mapper/test/TestCaseStepMapper.xml index 5c349b3..7568e4b 100644 --- a/test-test/src/main/resources/mapper/test/TestCaseStepMapper.xml +++ b/test-test/src/main/resources/mapper/test/TestCaseStepMapper.xml @@ -67,6 +67,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and assignment = #{assignment} and assertion = #{assertion} + ORDER BY step_num + + where case_id = #{caseId} and del_flag = '0' and parent_id is null + ORDER BY step_num +