引言
谈到性能测试,部分公司连专门用于性能测试的环境都没有,更别提性能测试框架/平台了。下面,笔者就“基于Jmeter的性能测试框架搭建”这个话题,谈谈自己的一些想法。
工具

Jmeter
Influxdb
Grafana
Telegraf
Jenkins
Ant
Gitlab
理念
-
测试人员只需专注脚本编写及性能结果分析。脚本提交Gitlab后自动触发构建,性能结果实时展现。
-
性能测试脚本统一管理。

性能测试框架
实现方法
-
依赖Jmeter的Backend Listener监听器,采集tps,响应时间,cpu,内存等信息至Influxdb时序数据库,然后再通过Grafana展现性能结果。
-
依赖Jenkins的webhook插件监听push事件,即push脚本至gitlab则触发Ant构建。
一、脚本上传小工具开发

压测小工具

开始构建
为了简便测试人员操作,特开发此压测小工具,实现功能如下:
-
上传脚本前,初始化本地git仓库。
-
克隆git仓库。
-
根据上传的脚本修改build.xml文件。
-
push脚本和build.xml文件。
使用JGit访问Gitlab,dom4j处理xml文件。pom.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.tool</groupId><artifactId>performanceTestTool</artifactId><version>1.0-SNAPSHOT</version><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>6</source><target>6</target></configuration></plugin></plugins></build><repositories><repository><id>jgit-repository</id><url>https://repo.eclipse.org/content/groups/releases/</url></repository></repositories><!-- Core Library --><dependencies><dependency><groupId>org.eclipse.jgit</groupId><artifactId>org.eclipse.jgit</artifactId><version>4.11.0.201803080745-r</version></dependency><!-- Smart HTTP Servlet --><dependency><groupId>org.eclipse.jgit</groupId><artifactId>org.eclipse.jgit.http.server</artifactId><version>4.11.0.201803080745-r</version></dependency><!-- AWT UI Helpers --><dependency><groupId>org.eclipse.jgit</groupId><artifactId>org.eclipse.jgit.ui</artifactId><version>4.11.0.201803080745-r</version></dependency><!-- JUnit Test Support --><dependency><groupId>org.eclipse.jgit</groupId><artifactId>org.eclipse.jgit.junit</artifactId><version>4.11.0.201803080745-r</version></dependency><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency></dependencies></project>
clone项目至本地仓库:
public static void cloneProject() throws Exception {//每次clone前先初始化Util.deletefile(localProject);File file = new File(localProject);try {//克隆代码库命令CloneCommand cloneCommand = Git.cloneRepository();cloneCommand.setURI(remoteRepoURI) //设置远程URI.setBranch("master") //设置clone下来的分支.setDirectory(file) //设置下载存放路径.setCredentialsProvider(usernamePasswordCredentialsProvider).call();} catch (GitAPIException e) {e.printStackTrace();}}
pull操作:
public static void pullFiles() throws IOException, GitAPIException {//git仓库地址Git git = new Git(new FileRepository(localProject+"/.git"));git.pull().setRemoteBranchName("master").setCredentialsProvider(usernamePasswordCredentialsProvider).call();}
push操作:
public static void pushFiles(String filePath,String commitMess) throws IOException, GitAPIException {File fileFrom = new File(filePath);File fileTemp = new File(localProject);File fileTo = new File(fileTemp.getAbsolutePath()+"/"+fileFrom.getName());fileTo.createNewFile();Util.copyFiles(fileFrom,fileTo); //拷贝脚本文件至git本地仓库Repository rep = new FileRepository(localProject+"\\.git");Git git1 = new Git(rep);git1.add().addFilepattern(fileFrom.getName()).call(); //此处必须使用本地仓库中需推送的文件名git1.add().addFilepattern("build.xml").call(); //push修改后的build文件//提交git1.commit().setMessage(commitMess).call();git1.push().setCredentialsProvider(usernamePasswordCredentialsProvider).call();}
build.xml文件配置:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><project basedir="." default="run" name="Ant"><tstamp><format pattern="yyyyMMddhhmm" property="time"/></tstamp><!-- jmeter路径--><property name="jmeter.home" value="D:\tools\PerformanceTesting\Jmeter_influxdb_grafana\apache-jmeter-3.1"/><!-- jmeter jtl测试报告生成路径--><property name="jmeter.result.jtl.dir" value="D:\tools\PerformanceTesting\Jmeter_influxdb_grafana\apache-jmeter-3.1\TestCase\report\jtl"/><!-- jmeter html测试报告生成路径--><property name="jmeter.result.html.dir" value="D:\tools\PerformanceTesting\Jmeter_influxdb_grafana\apache-jmeter-3.1\TestCase\report\html"/><!-- 参数化--><property name="ReportName" value="TestReport"/><property name="jmeter.result.jtlName" value="${jmeter.result.jtl.dir}/${ReportName}${time}.jtl"/><property name="jmeter.result.htmlName" value="${jmeter.result.html.dir}/${ReportName}${time}.html"/><target name="run"><antcall target="test"/><!--性能脚本构建时,生成报告时间太长,注释掉 --><!-- antcall target="report"/ --></target><target name="test"><taskdef classname="org.programmerplanet.ant.taskdefs.jmeter.JMeterTask" name="jmeter"/><jmeter jmeterhome="${jmeter.home}" resultlog="${jmeter.result.jtlName}"><!-- 构建路径,与jenkins上工作空间配置保持一致 --><testplans dir="D:\tools\PerformanceTesting\Jmeter_influxdb_grafana\apache-jmeter-3.1\TestCase\jenkins_jobworkspace" includes="测试.jmx"/><property name="jmeter.save.saveservice.output_format" value="xml"/></jmeter></target><target name="report"><xslt in="${jmeter.result.jtlName}" out="${jmeter.result.htmlName}" style="${jmeter.home}/extras/jmeter.results.shanhe.me.xsl"/><!-- 测试报告--><copy todir="${jmeter.result.html.dir}"><fileset dir="${jmeter.home}/extras"><include name="collapse.png"/><include name="expand.png"/></fileset></copy></target></project>
通过分析上面的build.xml文件,发现构建脚本由includes的值来定义,如果值为“*.jmx”,则会构建dir目录下所有的jmx文件。由于我们只需构建上传的脚本,那有必要修改build文件,使includes的值等于上传的脚本名称。
<testplans dir="D:\tools\PerformanceTesting\Jmeter_influxdb_grafana\apache-jmeter-3.1\TestCase\jenkins_jobworkspace" includes="测试.jmx"/>
修改build文件的includes值:
public static void modifyBuild(String filePath,String modifyName) throws ParserConfigurationException, IOException, SAXException {DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();DocumentBuilder db = dbf.newDocumentBuilder();Document doc = db.parse(new File(filePath)); // 读取xml文件NodeList targetList = doc.getElementsByTagName("testplans"); //获取testplans节点for (int i = 0; i < targetList.getLength(); i++) {Node case_node = targetList.item(i); // 第一个caseNo节点Element elem0 = (Element) case_node; // caseNo节点对象String target_name = elem0.getAttribute("includes");System.out.println("修改前includes = " + target_name); // 节点属性elem0.setAttribute("includes",modifyName);String target_name1 = elem0.getAttribute("includes");System.out.println("修改后includes = " + target_name1); // 节点属性}saveXml(filePath, doc);}
至此,第一阶段的工作已完成。当然,你也可以通过Git bash来push脚本触发构建,但是你得另外想办法来控制脚本的构建,因为Ant是根据build.xml文件来指定构建哪些脚本的。
二、配置Jenkins,Gitlab
-
安装jenkins插件。
Ant Plugin
Git plugin
GitLab Plugin
Gitlab Hook Plugin
可以在线安装,也可以下载本地安装,下载地址
-
jenkins-系统配置-全局工具配置。
配置jdk,git,ant。
jdk

git&ant
-
jenkins-项目-配置。
自定义空间与build.xml设置的构建路径保持一致,jenkins构建时,会把git仓库pull到该路径下。
<testplans dir="D:\tools\PerformanceTesting\Jmeter_influxdb_grafana\apache-jmeter-3.1\TestCase\jenkins_jobworkspace" includes="测试.jmx"/>

自定义空间

git源码管理
注意下图的Targets需同build.xml文件一致。
<target name="run"><antcall target="test"/><!--性能脚本构建时,生成报告时间太长,注释掉 --><!-- antcall target="report"/ --></target>

构建
由于依赖webhook来监听push事件触发构建,拷贝下图的URL,并在“高级”选项中生成“Secret token”,后续在gitlab添加webhook。

构建触发器

构建触发器
-
gitlab-webhook配置。
由于只需监听push事件,所有下图只勾选了Push events“”。添加webhook后,点击“Test”进行测试,如果返回200/201,则表明webhook配置成功。
webhook

webhook-test
如果不想使用Secret token配置webhook,也可按以下方式来配置:
打开jenkins的“设置”页面,找到API Token,然后在gitlab上添加webhook url即可,结构如下图2所示(UserId:APIToken@jenkins构建器url)。
API Token

三、配置Influxdb,Grafana,Telegraf
Influxdb+Grafana+Telegraf在笔者的另一篇文章<Jmeter排忧解难—性能测试监控>有提及,百度也大把文章,此处不再详述。
四、编写测试脚本
使用jmeter编写一个简单的测试脚本来进行测试,主要依赖Backend Listener监听器来集成influxdb。

测试脚本
测试脚本
五、性能结果分析
运行“一、脚本上传小工具开发”提及的压测小工具,就可以对性能结果做实时监控了。下图1仅对tps和响应时间做采集,由于笔者未在被测服务器上安装Telegraf,所以cpu,内存等数据暂不采集,有兴趣的童鞋可以自行探索。更详细的监控结果如图2所示。

性能测试结果

性能测试结果
六、构建日志
登录jenkins,可以查看详细的构建日志。

Jenkins构建日志
后续改进
其实以上框架还存在不少待改进之处,笔者后续再逐步解决。
-
Grafana性能图表实时展现,测试过程中需实时截图形成测试报告,不够人性化。
解决方案:自动生成测试报告并邮件通知。 -
Grafana性能图表需测试人员实时监控,人力成本较高。
解决方案:自动生成测试报告并邮件通知。 -
多脚本构建的话,无法区分Grafana展现的性能图表对应哪个脚本。
解决方案:传参区分脚本,并生成每个接口对应的测试报告。 -
如果考虑持续监控,可加入预警功能。
解决方案:依赖Grafana的预警功能。 -
未能自动生成测试报告。
解决方案:自动生成测试报告并邮件通知。 -
需登录jenkins停止脚本构建,操作不够便利。
解决方案:前端增加停止构建操作。 -
每次只能提交一个脚本进行构建。
解决方案:支持批量构建。
