JMeter+GoCD实现API自动化测试

    这篇记录一下从选型到初步实现方案成形踩过的坑。。。

前情提要&技术选型

    团队需要接口自动化测试用以检查:

       (1)接口返回的数据是否与预期结果一致。

       (2)检查接口的容错性,假如传递数据的类型错误时是否可以处理。

       (3)接口参数的边界值。例如,传递的参数足够大或为负数时,接口是否可以正常处理。

   在技术选型方面寻找了下面4种接口自动化的测试工具,对其优缺点进行了分析讨论:

    1. postman newman

    特点:可以快速构建请求、保存以供以后使用,并分析API发送的响应 -> 最简单,最容易上手

    不方便版本控制,可扩展性较差,case校验方式较少

    2. JMeter

    特点:提供了图形化界面,上手容易,插件丰富,除了api测试还可以做压力测试,可以使用非gui模式运行测试脚本

    测试脚本维护方便,可以将测试脚本复制,并且可以将某一部分单独保存

    使用参数化以及Jmeter提供的函数功能,可以快速完成测试数据的添加修改

    Jmeter脚本的维护需要保存为本地文件,

    测试文件为类xml格式,易读性较差,不方便做版本控制

    3. RobotFramework

    Robotframework的httplibrary库

    支持关键字驱动,提供图形化界面

    编写用例简单方便,可以用txt,tsv或html等格式编写用例

    测试报告和日志为html格式,易于阅读

    界面反应速度慢,经常卡死

    导入测试库可能会有异常

    没有非图形化操作方式,无法版本控制

    4. Python+requests / Java+httpclient

    简洁优雅,功能强大,方便版本控制,可根据team需求自定义测试结构

    对于Dev比较友好,没有可视化的报告(可以考虑加入如log4j这样的日志工具

    一开始我是推荐第4个使用httpclient or requests库这样的来写自动化脚本,但是考虑到对测试人员的友好,同时未来的性能测试/压力测试,最后选择了JMeter。

实践过程

    测试框架的整体思路是使用JMeter编写http请求测试脚本,在QA环境中模拟用户进行真实的http请求,使用断言判断返回的状态码和返回信息。并将JMeter集成进GoCD的持续集成平台,打一个docker运行在云主机中,对自动化测试使用单独的pipeline,当代码被部署到环境时自动run自动化测试的pipeline。

    梦想很丰满,显示很骨感,在实践过程中发现了若干问题,具体问题如下:

1. json数据如何动态获取

    在调用api之前都需要发送一个申请access token的http请求,再使用access token去调用其他的api。返回的数据样式如下👇

{ "access_token": "xxx", "type1": "this is type1", "type2": 1, "type3": "this is type3"}

    而且这个access token还是一小时一刷新的,只好动态获取其值,为了提取access_token里的值,使用了正则表达提取理器(Regular Expression Extractor)

    Name of created variable是提取出变量的名称,Regular Expression是待匹配正则表达式,这里我用的是"access_token":"(.+?)",需要注意的一点是,当时我拿着postman生成的结果去做正则匹配,匹配match之后拿到JMeter里,但是在JMeter上匹配出来的结果始终match不上,调了半天发现Postman里的返回Json数据美化了,有了多余的空格,而Jmeter里拿回来的数据,所以始终match不上。Template:用$$引用起来,如果在正则表达式中有多个正则表达式,则可以是$2$$3$等等,表示解析到的第几个值给title。如:$1$表示解析到的第1个值。Match No. 匹配的第n个值,如果选0就代表随机选,因为这里只有一个access token,所以给0也无所谓啦~~~,Default Value缺省值,如果找不到就给一个默认的值。

    验证一下提取的结果,为了观察变量的值,可以添加一个Debug Sampler,验证一下发现可以成功提取,欧耶~~

    但是另一个问题接踵而至,为了区分不同的http请求,这里使用了多个Thread来run测试脚本,当前提取出来的token只能用在当前Thread里,要么全部http请求写在一个Thread里,要么就分开使用全局变量。前者很明显是不make sense的,如果想只运行部分测试脚本就变得异常困难,那么就要如何将提取出的变量变为全局变量。

    找了半天,找到了BeanShell Sampler,采用通过添加shell脚本的方式来将本线程的变量变为全局变量

    如上图,它将本线程内的token变量,加上了全局引用,引用名为newtoken,在实际使用的时候就可以用${__property(newtoken)}来全局引用token。啊对还有一点,当这个测试脚本run一遍之后,变量值就会被默认存储,这个时候再去run其他独立的测试线程,也可以使用刚刚得到的参数,而不用每次都从头到尾都run一遍了。

2. 加密信息如何处理

    HTTP请求中的header有一些账号密码是私密信息,存入团队的git仓库中可能会造成密码泄露,如果贸然的将未加密的信息上传到代码仓库里恐怕我就见不到明天的太阳了,so分两步走,因为待测试的jmx文件有很多,首先将所有secret信息统一存入一个文件,在测试文件里直接对其引用就好,再单独对这个secret信息进行加密即可。JMeter中的Config中有一个CSV Data Set Config,支持使用csv(据说txt的文本格式也行,我没试过)格式的数据导入,方便批量测试数据的导入,当然这里我没用上哈,我只是配了一行数据即待加密的信息,配置如下:

    这样就可以在JMeter里直接调用${_token}和${_key}这两个变量啦。

    解决了加密信息统一存储的问题,接下来就要对信息进行加密了,将加密后的信息存入代码库中,run的时候将其解密就行,这里使用了git-crypt对其进行加密,在配置pipeline的时候把解密的命令配进去就妥妥的了,具体过程不表,因为是Dev帮忙写的=。=

3. 错误的HTTP状态码如何让测试通过

    对于一些测试非法情况的场景,我们期望其返回的HTTP Code就是404,但是不期望它是502,这里加入了断言,判断其返回的状态码是否是404,但是JMeter依然判定我测试失败,搞了半天才发现需要在断言里把Ignore Status给勾上,如下图所示,如果遇到了400,断言通过,同时忽略这个错误状态,测试就pass啦~

4. 如何集成进GoCD

    至此JMeter的配置已经完全结束,现在已经可以全自动的实时获取token,并使用token发送http请求。同时对敏感信息进行了加密,保证不被泄露。下面最坑的地方来了,因为团队使用的持续集成(CI)工具是GoCD,如何将其集成进GoCD便成为了我最头疼的问题,GoCD是ThoughtWorks开发的一款持续集成工具,但是生态不如同为CI工具的Jenkins那么友好,Jenkins直接提供了JMeter配置方式。针对如何集成进GoCD我先后想了两个方案,到最后终于填坑,叙述一下心路历程。

4.1 方案1:写一个shell脚本

    首先第一个考虑到的思路是使用JMeter的无GUI模式,即用命令行的方式运行一个测试脚本,JMeter可以使用下面的命令来通过命令行的方式来run一个测试脚本:

jmeter -n -t test2.jmx -l result.jtl

    -n即打开no gui模式,-t指定配置文件,-l指定测试report的名称/地址。run一遍之后我们可以看到命令行有如下输出:

    JMeter会提供一个general的report,目前有意义的数据只有Error的数量,如果想看更详细的可以打开我们指定的result.jtl

    这其实也是个csv数据啦,观察到第10列failureMessage如果测试通过则字符串为空,如果不通过字符串不为空比如:Test failed: code expected to match /201/。

    但是当我把脚本run在GoCD上的时候,发现无论有几个Error都不会导致pipeline的failed,全部passed(如下图),仰天长啸天要亡我,寻思了半天想了想只能从0开始造轮子了,因为GoCD是根据程序的返回值来判断成功或失败,如果我让它返回的不是非0不就可以让pipeline挂掉了嘛=。=

    至此方案1的思路就已经初步有了,通过判断输出的测试report中每行第10个数据项(failureMessage)是否为空来推断是否测试通过,或者可以通过命令行标准输出中的Error数量来判断,只要Err:后面的数字不为0即为测试失败,直接输出得到的数值即可,想了想选择了第一个方案去尝试,因为考虑到需要在控制台输出详细的错误原因方便调试。这里尝试使用shell脚本来完成这个任务。

    shell脚本的大体思路是显示输出一个命令,再获取输出的测试report,对于每一行字符串(即run的每个测试脚本)按照逗号分隔,判断分隔出的第10个字符串是否为空,如果不为空就输出错误信息,最后返回错误代码即可让pipeline为failed,代码如下:

    但是这个方案我自己都觉得很不健壮,因为我一旦在测试用例名称里加了一个逗号,都会导致这个脚本的崩溃。同时需要在主机里安装JMeter的客户端,那就更费事了。。。Mac上好歹可以brew安装,Linux上就要一顿操作猛如虎了,果断弃坑。

4.2 方案2:使用gradle插件

4.2.1 配置gradle插件

    此路不同自有别路,同事提出了一个新思路,JMeter有Gradle插件,可以直接调用,比我的方案1不知道高到哪里去了,我就赶紧琢磨了一下,这是JMeter的Gradle插件link,整理了一下发现真是好用啊。。。(虽然只有40颗星),基本用法就三条

    gradle jmGui -> 通过gradle命令打开一个JMeter窗口客户端

    gradle jmRun -> 运行JMeter脚本    

    gradle jmReport ->生成HTML的测试报告,更易读

    这样不但解决了安装JMeter的问题,也搞定了运行脚本的痛,这个插件可以判断是否有测试失败的脚本,如果有则pipeline构建失败。

    具体的配置可以去网站自行查找,这里只说一点坑的地方,我在尝试使用jmReport生成报告的时候死活生成不了,说找不到result文件,后来在GitHub里的答疑板块参考了德国老哥的gradle配置,同时好像需要从本地JMeter里copy一个jmeter-results-detail-report_21.xsl,再运行jmReport就好使了,具体配置如下:

4.2.2 输出report到控制台

    终于搞定了Gradle插件,美滋滋的将其上到GoCD的Pipeline上,但是发现这玩意死活不给错误信息,只告诉我错了,自己去看log,我云主机咋看log嘛。。。找了半天都没找到可以用来输出的参数,甚至想去改他插件了。。。痛定思痛想到了因为这里我指定输出的test report为xml格式,我可以通过Groovy自带的XML解析来完成这个,通过解析<failureMessage>判断是否为空,如果不为空输出这一个区域的附加信息,代码如下:

    至此全部配置完成,GoCD的Pipeline就不表了,正常配就ok,最后的效果图

JMeter脚本run失败的情况
读取测试Report并输出错误原因

    一点拙见,想法还不成熟,欢迎大佬们点评指正~

推荐阅读更多精彩内容