diff --git a/test-common/src/main/java/com/test/common/utils/MySQLExecutor.java b/test-common/src/main/java/com/test/common/utils/MySQLExecutor.java index c50bcf5..d43da20 100644 --- a/test-common/src/main/java/com/test/common/utils/MySQLExecutor.java +++ b/test-common/src/main/java/com/test/common/utils/MySQLExecutor.java @@ -22,14 +22,17 @@ public class MySQLExecutor { * @param user 数据库用户名 * @param password 数据库密码 * @param columnNameList 所有列名集合 + * @param sqlSpecialResultMap 存储sql特有的响应信息字段结果值 * @return 查询结果(每行数据为一个 Map) */ - public static List> executeQuery(String sql, String url, String user, String password, List columnNameList) { + public static List> executeQuery(String sql, String url, String user, String password, List columnNameList, Map sqlSpecialResultMap) { List> result = new ArrayList<>(); try (Connection connection = DriverManager.getConnection(url, user, password); Statement statement = connection.createStatement()) { - // 执行查询,限制最大100行 - try (ResultSet resultSet = statement.executeQuery(sql.replaceAll(";", "") + " LIMIT 100")) { + boolean isResultSet = statement.execute(sql.replaceAll(";", "")); + if (isResultSet) { + // 处理查询结果 + ResultSet resultSet = statement.getResultSet(); // 获取结果集的元数据 ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); @@ -52,6 +55,10 @@ public class MySQLExecutor { } result.add(row); } + } else { + // 处理非查询语句 + int rowsAffected = statement.getUpdateCount(); + sqlSpecialResultMap.put("${AFFECT_ROWS}", String.valueOf(rowsAffected)); } } catch (SQLException e) { throw new RuntimeException(e); diff --git a/test-test/src/main/java/com/test/test/controller/TestCaseController.java b/test-test/src/main/java/com/test/test/controller/TestCaseController.java index df7f278..0f3f7fb 100644 --- a/test-test/src/main/java/com/test/test/controller/TestCaseController.java +++ b/test-test/src/main/java/com/test/test/controller/TestCaseController.java @@ -17,6 +17,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.concurrent.CompletableFuture; /** * 用例Controller @@ -87,6 +88,10 @@ public class TestCaseController extends BaseController { @PostMapping("/run") public AjaxResult run(@RequestBody IDQO qo) { log.info("执行用例 id:{}, jmeterHomePath:{}", qo.getId(), jmeterHomePath); - return success(testCaseService.executeTestCaseById(qo.getId(), jmeterHomePath)); + // 异步执行任务 + CompletableFuture.runAsync(() -> { + testCaseService.executeTestCaseById(qo.getId(), jmeterHomePath); + }); + return toAjax(true); } } diff --git a/test-test/src/main/java/com/test/test/controller/TestTaskController.java b/test-test/src/main/java/com/test/test/controller/TestTaskController.java index c200821..f5a0666 100644 --- a/test-test/src/main/java/com/test/test/controller/TestTaskController.java +++ b/test-test/src/main/java/com/test/test/controller/TestTaskController.java @@ -6,16 +6,18 @@ import com.test.common.core.domain.AjaxResult; import com.test.common.core.page.TableDataInfo; import com.test.common.enums.BusinessType; import com.test.common.utils.DateUtils; -import com.test.test.domain.TestCase; +import com.test.common.utils.SecurityUtils; import com.test.test.domain.TestTask; import com.test.test.domain.qo.GroupIdQO; import com.test.test.domain.qo.IDQO; import com.test.test.service.ITestTaskService; import jakarta.annotation.Resource; +import org.springframework.beans.factory.annotation.Value; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.concurrent.CompletableFuture; /** * 定时任务Controller @@ -23,6 +25,10 @@ import java.util.List; @RestController @RequestMapping("/test/task") public class TestTaskController extends BaseController { + + @Value("${test.jmeterHomePath:/opt/apache-jmeter}") + private String jmeterHomePath; + @Resource private ITestTaskService testTaskService; @@ -73,5 +79,18 @@ public class TestTaskController extends BaseController { return toAjax(testTaskService.deleteTestTaskById(qo.getId())); } + /** + * 执行任务 + */ + @Log(title = "任务", businessType = BusinessType.OTHER) + @PostMapping("/run") + public AjaxResult run(@RequestBody IDQO qo) { + // 异步执行任务 + CompletableFuture.runAsync(() -> { + testTaskService.executeTestTaskById(qo.getId(), 2, null, jmeterHomePath, SecurityUtils.getUsername()); + }); + return toAjax(true); + } + } 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 index ba51654..a467b1d 100644 --- 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 @@ -280,8 +280,10 @@ public class TestCaseServiceImpl implements ITestCaseService } String url = "jdbc:mysql://"+ testDatasource.getHost() +":"+ testDatasource.getPort() +"/" + testDatasource.getDbName(); List columnNameList = new ArrayList<>(); + // sql查询特有字段结果存储 + Map sqlSpecialResultMap = new HashMap<>(); // 获取所有sql查询集合对象 - List> resultMapList = MySQLExecutor.executeQuery(testCaseStep.getSqlCommand(), url, testDatasource.getUsername(), testDatasource.getPassword(), columnNameList); + List> resultMapList = MySQLExecutor.executeQuery(testCaseStep.getSqlCommand(), url, testDatasource.getUsername(), testDatasource.getPassword(), columnNameList, sqlSpecialResultMap); if (!CollectionUtils.isEmpty(resultMapList)) { SqlResult sqlResult = new SqlResult(); sqlResult.setColumnNameList(columnNameList); @@ -293,7 +295,7 @@ public class TestCaseServiceImpl implements ITestCaseService testCaseResult.setSqlResult(gson.toJson(sqlResult)); // 处理参数提取 Map assignmentResultMap = new HashMap<>(); - String assignmentResult = this.dealDataSourceTestCaseStepAssignment(resultMapList, assignmentResultMap, assignment); + String assignmentResult = this.dealDataSourceTestCaseStepAssignment(resultMapList, assignmentResultMap, sqlSpecialResultMap, assignment); testCaseResult.setAssignment(assignmentResult); // 根据提取结果处理校验规则 String assertionResult = this.dealTestCaseStepAssertion(assignmentResultMap, assertion); @@ -522,10 +524,11 @@ public class TestCaseServiceImpl implements ITestCaseService * 处理数据源响应字段提取 * @param resultMapList * @param assignmentResultMap + * @param sqlSpecialResultMap 存储sql特有的响应信息字段结果值 * @param assignment * @return 处理后的提取结果 */ - private String dealDataSourceTestCaseStepAssignment(List> resultMapList, Map assignmentResultMap, String assignment) { + private String dealDataSourceTestCaseStepAssignment(List> resultMapList, Map assignmentResultMap, Map sqlSpecialResultMap, String assignment) { if (!StringUtils.isEmpty(assignment) && !"[]".equals(assignment)) { Gson gson = new Gson(); String responseBody = gson.toJson(resultMapList); @@ -547,6 +550,20 @@ public class TestCaseServiceImpl implements ITestCaseService String value = JMeterUtil.extractFieldValue(responseBody, path); ruleResult.setValue(value); } + } else if ("${RESULT_DATA_LENGTH}".equals(content)) { + if (!CollectionUtils.isEmpty(resultMapList)) { + String value = String.valueOf(resultMapList.size()); + ruleResult.setValue(value); + } else { + ruleResult.setValue("0"); + } + } else if ("${AFFECT_ROWS}".equals(content) || "${CHANGE_ROWS}".equals(content)) { + String affectRows = sqlSpecialResultMap.get("${AFFECT_ROWS}"); + if (affectRows == null) { + ruleResult.setValue("0"); + } else { + ruleResult.setValue(affectRows); + } } assignmentResultMap.put(rule.getName(), ruleResult.getValue()); ruleResultList.add(ruleResult); @@ -576,6 +593,13 @@ public class TestCaseServiceImpl implements ITestCaseService String source = rule.getSource(); String target = rule.getTarget(); String fn = rule.getFn(); + if (source.equals("${RESPONSE_BODY}")) { + source = "responseBody"; + } else if (source.equals("${RESPONSE_CODE}")) { + source = "responseCode"; + } else if (source.equals("${RESPONSE_HEADER}")) { + source = "responseHeader"; + } String value = assignmentResultMap.get(source); ruleResult.setValue(value); if ("等于".equals(fn)) { diff --git a/test-test/src/main/java/com/test/test/service/impl/TestCaseStepServiceImpl.java b/test-test/src/main/java/com/test/test/service/impl/TestCaseStepServiceImpl.java index 037f0ca..2694885 100644 --- a/test-test/src/main/java/com/test/test/service/impl/TestCaseStepServiceImpl.java +++ b/test-test/src/main/java/com/test/test/service/impl/TestCaseStepServiceImpl.java @@ -63,7 +63,7 @@ public class TestCaseStepServiceImpl implements ITestCaseStepService { int i = 1; for (TestCaseResult testCaseResult : testCaseResultList) { String dateStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, testCaseResult.getExecuteTime()); - testCaseResult.setTitle("结果" + i + "(" + dateStr + "),状态:" + testCaseResult.getStatus()); + testCaseResult.setTitle("结果" + i + "(" + dateStr + "),状态:" + testCaseResult.getStatus() + ",耗时(ms):" + testCaseResult.getUseTime()); i ++; } } diff --git a/test-test/src/main/java/com/test/test/service/impl/TestTaskServiceImpl.java b/test-test/src/main/java/com/test/test/service/impl/TestTaskServiceImpl.java index da43bf9..4f12336 100644 --- a/test-test/src/main/java/com/test/test/service/impl/TestTaskServiceImpl.java +++ b/test-test/src/main/java/com/test/test/service/impl/TestTaskServiceImpl.java @@ -110,9 +110,8 @@ public class TestTaskServiceImpl implements ITestTaskService { @Override public boolean executeTestTaskById(Long id, Integer triggerType, String environment, String jmeterHomePath, String operUser) { TestTask testTask = testTaskMapper.selectTestTaskById(id); - if (testTask == null || testTask.getStatus() == null - || testTask.getStatus() > 0 || "2".equals(testTask.getDelFlag())) { - log.error("定时任务已删除或未启用,不能执行!"); + if (testTask == null || "2".equals(testTask.getDelFlag())) { + log.error("定时任务已删除,不能执行!"); return false; } TestTaskLog testTaskLog = new TestTaskLog(); diff --git a/test-ui/src/api/test/task.js b/test-ui/src/api/test/task.js index 3d892a2..c4183ca 100644 --- a/test-ui/src/api/test/task.js +++ b/test-ui/src/api/test/task.js @@ -44,3 +44,12 @@ export function delTask(id) { data: {id} }) } + +// 执行用例 +export function runTask(id) { + return request({ + url: '/test/task/run', + method: 'post', + data: {id} + }) +} diff --git a/test-ui/src/views/test/case/detail/index.vue b/test-ui/src/views/test/case/detail/index.vue index 00ce270..c0e9e30 100644 --- a/test-ui/src/views/test/case/detail/index.vue +++ b/test-ui/src/views/test/case/detail/index.vue @@ -1,13 +1,29 @@ + + + + + + + + + @@ -49,12 +64,18 @@ @@ -79,9 +100,11 @@ import "codemirror/lib/codemirror.css"; import "codemirror/mode/sql/sql"; // 语言模式 import "codemirror/theme/dracula.css"; import "codemirror/addon/display/autorefresh"; +import {getCaseStepResultLog} from "@/api/test/caseStep"; export default { components: { codemirror }, + dicts: ['extract_type','assert_condition'], props: { form: { type: Object @@ -93,6 +116,33 @@ export default { data() { return { activeName: "sql", + extract_object: [{ + value: '${RESULT_DATA}', + label: '${RESULT_DATA}' + },{ + value: '${RESULT_DATA_LENGTH}', + label: '${RESULT_DATA_LENGTH}' + }, + // { + // value: '${INSERT_ID}', + // label: '${INSERT_ID}' + // }, + { + value: '${AFFECT_ROWS}', + label: '${AFFECT_ROWS}' + },{ + value: '${CHANGE_ROWS}', + label: '${CHANGE_ROWS}' + }], + resultLog: { + requestHeader: '', + requestBody: '', + responseHeader: '', + responseBody: '', + sqlResult: '' + }, + activeNames: 'sqlResult', + activeTabs: 'sqlResult', editorOptions: { autoRefresh: true, mode: "sql", // 代码语言 @@ -102,6 +152,16 @@ export default { } } }, + methods: { + handleLogChange(val) { + const selectedItem = this.form.testCaseResultList.find(item => item.title === val); + if (selectedItem) { + getCaseStepResultLog(selectedItem.id).then(res => { + this.resultLog = res.data; + }); + } + }, + } } diff --git a/test-ui/src/views/test/case/detail/step.vue b/test-ui/src/views/test/case/detail/step.vue index bd5fbc9..0d68ef8 100644 --- a/test-ui/src/views/test/case/detail/step.vue +++ b/test-ui/src/views/test/case/detail/step.vue @@ -1,14 +1,18 @@ @@ -48,18 +57,22 @@ import page3 from "./page3.vue" import page4 from "./page4.vue" import {listHttp} from "@/api/test/http"; import {listDatasource} from "@/api/test/database"; +import Report from "@/views/test/case/report/index.vue"; export default { dicts: ['step_type'], - components: {draggable, page1, page2, page3, page4}, + components: {Report, draggable, page1, page2, page3, page4}, data() { return { id: '', + isVisible: false, loading: false, + open: false, activeName: null, list: [], hosts: [], datasourceList: [], + caseSID: null, }; }, created() { @@ -145,11 +158,17 @@ export default { }, methods: { + caseEditVisible() { + this.isVisible = true; + this.$emit('data-from-child', false); + }, handleRun(id) { this.$modal.confirm('是否确认执行用例?').then(function () { return runCase(id); }).then(() => { this.$modal.msgSuccess("执行成功"); + this.caseSID = res.msg + this.open = true; }); }, // 拖拽结束后更新排序值 @@ -197,10 +216,14 @@ export default { }); }, submit() { + this.isVisible = false; + this.$emit('data-from-child', true); this.$emit("submit", this.list); }, cancel() { - this.$tab.closeOpenPage({path: "/case"}); + this.isVisible = false; + this.$tab.refreshPage({path: "/case/detail/?id=" + this.id}) + // this.$tab.closeOpenPage({path: "/case"}); }, handleDel(index) { this.list.splice(index, 1) diff --git a/test-ui/src/views/test/case/report/index.vue b/test-ui/src/views/test/case/report/index.vue index dd74490..78b36bc 100644 --- a/test-ui/src/views/test/case/report/index.vue +++ b/test-ui/src/views/test/case/report/index.vue @@ -3,23 +3,23 @@ - + - + - + - + @@ -28,12 +28,16 @@
{{item.sqlCommand}}
- - + + + + + + @@ -42,6 +46,8 @@ + +
@@ -57,55 +63,33 @@ - - - - - - - - - - + + + - - - - - - - - - - + + + - +
{{item.sqlCommand}}
- - + + + + + + @@ -114,6 +98,8 @@ + +
diff --git a/test-ui/src/views/test/task/index.vue b/test-ui/src/views/test/task/index.vue index c4e181d..a596b45 100644 --- a/test-ui/src/views/test/task/index.vue +++ b/test-ui/src/views/test/task/index.vue @@ -10,7 +10,7 @@ - + @@ -29,7 +29,7 @@