Jmeter因其开源、扩展性好等原因,已经被业界广泛接受,有越来越多的企业选择Jmeter作为主要的压测工具。如果你想大幅降低Jmeter工具是使用的技术门槛,或者想大幅提升Jmeter脚本编写的效率,那么可以尝试开发功能来自动化生成相应脚本。
通常来说,有两种方式来自动化生成Jmeter脚本:
1.解析并生成jmx文件(xml文件)。在Jmeter工具中编写和配置脚本完成以后,都可以保存为jmx文件,这个文件格式是标准的xml文件,其基本是hashTree树的结构。通过研究现有jmx文件,可以找到其构造规律和方法。之后,根据性能测试需求,就可以构造出想要的jmx脚本了。
2.源码解析和改造。通过改造Jmeter界面和功能,结合业务需求(比如是Excel文件的需求)在Jmeter窗口中自动生成测试计划树。
下面我们就来详细讲解下第2种方法:
【一、实现功能概述】
将性能测试需求结构化,比如Excel;定制Jmeter菜单,增加一个导入的菜单功能;该功能可选中Excel,解析Excel,并将需求自动转换为Jmeter测试计划、线程组、HTTP请求、JSON消息体数据、CSV配置等内容。
【二、性能测试需求结构化】
需求方面,可以根据各个企业情况自己定义,建议是整理到Excel中,便于后续代码解析使用。下图是样例:
【三、环境准备】+【定制Jmeter菜单】
可以参考我写的另一篇文章:
【四、新建目录-存放数据文件】
该目录主要存放生成的jmx文件、CSV文件
核心代码如下:
//新建目录用于存放数据文件 String dataDir = f.getParent()+"\\数据文件"; if(!DirectoryOperations.isDirectoryExists(dataDir)) { DirectoryOperations.createNewDirectory(dataDir); } else{ File directory = new File(dataDir); deleteDirectory(directory); DirectoryOperations.createNewDirectory(dataDir); }
注意:
1.f为File类型,就是第二步中的Excel文件。
2. DirectoryOperations是自定义的类,可以自己定义一些文件目录操作的函数,供后续调用。
【五、解析Excel】
核心代码:
Map<String,String> mapData = readExcel(f);
//自定义readExcel函数,用于解析Excel。以下示例代码: Workbook workbook = new XSSFWorkbook(f); Sheet sheet = workbook.getSheetAt(0); Row row = sheet.getRow(1); Cell cell = row.getCell(0); if (Objects.equals(cell.getStringCellValue(), "测试项目:")) { if (row.getCell(1) == null) { System.out.println("测试项目不能为空!"); } else { mapData.put("测试项目:", row.getCell(1).getStringCellValue()); } }
注意:
1.解析完Excel后,将键值对存储到mapData中。
2. readExcel需要自定义。
3.建议是用poi的包解析Excel,我使用的版本是4.0.0
【六、构造测试计划树】
核心代码:
HashTree fhTree = createFHTree(mapData, dataDir);
注意:
1.自定义createFHTree函数,构造测试计划树。
2. createFHTree函数需要第四、第五步结果作为参数。
【六-1、构建测试计划】
核心代码:
//测试计划基本参数设置 TestPlan testPlan = new TestPlan(getMapValue(mapData,"测试项目:")); testPlan.setFunctionalMode(false); testPlan.setTearDownOnShutdown(true); testPlan.setComment(getMapValue(mapData,"测试项目注释:")); testPlan.setSerialized(false); //测试计划中用户自定义变量的设置,同时更新到Json请求中 Arguments userDefinedArgs = new Arguments(); String jsonString = getMapValue(mapData, "JSON请求:"); //需要自己根据Excel需求中的内容,自己替换JsonString中的内容,替换部分的示例如下: "ruleidid": "${__RandomFromMultipleVars(ruleidid1|ruleidid2|ruleidid3|ruleidid4|ruleidid5|ruleidid6)}", //需要自己根据Excel需求中的内容,添加参数变量,示例代码如下: userDefinedArgs.addArgument("ruleidid1", "1"); userDefinedArgs.addArgument("ruleidid1", "2"); //增加测试计划的参数变量 testPlan.setUserDefinedVariables(userDefinedArgs); //测试计划界面刷新 testPlan.setProperty(TestElement.TEST_CLASS, TestPlan.class.getName()); testPlan.setProperty(TestElement.GUI_CLASS, TestPlanGui.class.getName());
【六-2、构建线程组】
核心代码:
SetupThreadGroup threadGroup = new SetupThreadGroup(); threadGroup.setName(getMapValue(mapData, "测试项目:") + "-线程组"); threadGroup.isEnabled(); //获取Excel中的并发数,并设置到线程组 String numThreads = getMapValue(mapData, "性能要求-并发用户数:"); if (!Objects.equals(numThreads, "")) { if (numThreads.contains(".")) { int dotIndex = numThreads.indexOf("."); String newNumThreads = numThreads.substring(0, dotIndex); threadGroup.setNumThreads(Integer.parseInt(newNumThreads)); } else { threadGroup.setNumThreads(Integer.parseInt(numThreads)); } } //其他属性都可以通过SetupThreadGroup去设置,示例如下: threadGroup.setRampUp(0); threadGroup.setScheduler(false); threadGroup.setProperty(TestElement.TEST_CLASS, ThreadGroup.class.getName()); threadGroup.setProperty(TestElement.GUI_CLASS, ThreadGroupGui.class.getName());
【六-3、构建CSV配置器】
核心代码:
// 写入CSV文件,同时更新CSV配置器,同时更新Json请求 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); String timeStamp = dateFormat.format(new Date()); // paramName可以自定义String,也可以从Excel文件中获取 String filePath = dataDir + "\\CSVData" + "-" + paramName + "-" + timeStamp + ".csv"; // 写入csv文件,csv需要根据Excel文件,自己构造String try (FileWriter writer = new FileWriter(filePath); BufferedWriter bufferedWriter = new BufferedWriter(writer)) { bufferedWriter.write(csvString); } catch (IOException e) { e.printStackTrace(); } //类似于六-1,需要自己根据Excel需求中的内容,自己构建和更新JsonString中的内容,替换部分的示例如下 "username":"${username}", "password":"${password}" //设置CSV属性 CSVDataSet csvData = new CSVDataSet(); csvData.setName(getMapValue(mapData, "测试项目:") + "-CSV文件设置"); csvData.setProperty("filename", filePath); csvData.setProperty("fileEncoding", "UTF-8"); //注意paramName、paramValue需自己构造,并且于上一步的变量比如username、password保持一致。 csvData.setProperty("variableNames", paramName + "," + paramValue); //其他CSV配置器参数,都可以通过CSVDataSet类设置,示例如下: csvData.setDelimiter(","); csvData.setShareMode("All threads"); csvData.setProperty(TestElement.TEST_CLASS, CSVDataSet.class.getName()); csvData.setProperty(TestElement.GUI_CLASS, TestBeanGUI.class.getName()); //csv配置器添加到测试计划中 HashTree csvTree = new HashTree(); csvTree.add(csvData); testPlan.addTestElement(csvData); tree.add(testPlan, csvTree);
【六-4,构建HTTP头】
HeaderManager headerManager = new HeaderManager(); headerManager.setName(getMapValue(mapData, "测试项目:") + "-请求头管理器"); headerManager.isEnabled(); headerManager.setComment(getMapValue(mapData,"测试项目:")+"-请求头管理器"); headerManager.add(new Header("Content-Type", "application/json")); headerManager.setProperty(TestElement.TEST_CLASS, HeaderManager.class.getName()); headerManager.setProperty(TestElement.GUI_CLASS, HeaderPanel.class.getName());
【六-5,构建HTTP请求】
核心代码:
HTTPSampler httpSampler = new HTTPSampler(); //注意下面参数值,可根据Excel内容,去自己获取 httpSampler.setProtocol(protocol); httpSampler.setDomain(domain); httpSampler.setPort(443); httpSampler.setPath(path); httpSampler.setMethod(httpMethod); httpSampler.setName(httpName); httpSampler.setContentEncoding("UTF-8"); //设置http请求的 Json参数 Arguments args = new Arguments(); //jsonString,就是六-1、六-3构建的新的json请求 args.addArgument("", jsonString); convertArgumentsToHTTP(args); httpSampler.setArguments(args); httpSampler.setPostBodyRaw(true); //http请求界面刷新 httpSampler.setProperty(TestElement.TEST_CLASS, HTTPSampler.class.getName()); httpSampler.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName());
【六-6,构建查看树结果】
核心代码:
ResultCollector resultTree = new ResultCollector(new Summariser()); resultTree.setName(getMapValue(mapData, "测试项目:") + "-察看结果树"); resultTree.setProperty(TestElement.TEST_CLASS, ResultCollector.class.getName()); resultTree.setProperty(TestElement.GUI_CLASS, ViewResultsFullVisualizer.class.getName());
【六-7,构建汇总报告】
核心代码:
ResultCollector resultCollector = new ResultCollector(new Summariser()); resultCollector.setName(getMapValue(mapData, "测试项目:") + "-汇总报告"); resultCollector.setProperty(TestElement.TEST_CLASS, ResultCollector.class.getName()); resultCollector.setProperty(TestElement.GUI_CLASS, SummaryReport.class.getName());
【六-8,Jmeter树的构建】
HashTree subTree = new HashTree(); subTree.add(threadGroup); subTree.add(threadGroup, httpSampler); subTree.add(threadGroup, headerManager); subTree.add(threadGroup, resultTree); subTree.add(threadGroup, resultCollector); testPlan.addThreadGroup(threadGroup); tree.add(testPlan, subTree); return tree;
【六-9、保存jmx数据文件】
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); String timeStamp = dateFormat.format(new Date()); String jmxFileName = dataDir + "_" + timeStamp + ".jmx"; SaveService.saveTree(fhTree, new FileOutputStream(jmxFileName));
【六-10、将以上构造的整体数据加载Jmeter界面】
Load.insertLoadedTree(1, fhTree, false); FileDialoger.setLastJFCDirectory(f.getParentFile().getAbsolutePath()); guiPackage.updateCurrentGui(); guiPackage.getMainFrame().repaint();
【七、结果验证】
【后记】
源码的解析,自定义代码,为后续其他扩展提供了可能。在自动生成了jmx脚本后,还可以继续通过代码执行脚本、生成报告等操作。当然由于本地资源的问题,可能出现outofmemory等问题,这时也可以考虑调整jvm参数,或进行分布式部署等方案。