基于Jmeter的性能测试框架搭建

引言

谈到性能测试,部分公司连专门用于性能测试的环境都没有,更别提性能测试框架/平台了。下面,笔者就“基于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

    webhook.png
三、配置Influxdb,Grafana,Telegraf

Influxdb+Grafana+Telegraf在笔者的另一篇文章<Jmeter排忧解难—性能测试监控>有提及,百度也大把文章,此处不再详述。

四、编写测试脚本

使用jmeter编写一个简单的测试脚本来进行测试,主要依赖Backend Listener监听器来集成influxdb。


测试脚本

测试脚本
五、性能结果分析

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


性能测试结果

性能测试结果
六、构建日志

登录jenkins,可以查看详细的构建日志。


Jenkins构建日志

后续改进

其实以上框架还存在不少待改进之处,笔者后续再逐步解决。

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

《基于Jmeter的性能测试框架搭建》改进一
《基于Jmeter的性能测试框架搭建》改进二

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容