Jmeter 接口自动化-脚本数据分离实例

1、 背景:

 为了让你们更加的了解Jmeter,而且使用起来游刃有余。
这篇咱们主要讲一下,如何优雅的使用Jmeter一步步的实现接口自动化,完成脚本与数据分离,把可能对Jmeter脚本的维护转移到csv文本中,下降接口变动时对脚本的维护,最终目标是实现写好接口自动化脚本后,接口变动的维护都只要操做csv文件。java

Jmeter脚本,数据和报告地址:
https://github.com/grizz/jmeter-mastergit

testerhome地址:https://testerhome.com/topics/13029github

2、实例

先介绍一个Jmeter的函数-》csvRead函数,后续介绍使用的会比较多,熟悉的伙伴能够直接跳过。正则表达式

一、csvRead函数使用:

csvRead函数是从外部读取参数,能够从一个文件中读取多个参数。 
使用步骤:
一、先新建一个文件,例如test.csv(或test.txt),里面的数据存放为
grizz,qq1111
jiezai,qq1111json

文件为用户名和密码,用逗号隔开,每一列表示一种参数,每一行则表示一组参数。数组

二、选项-》函数助手对话框-》函数助手,打开Jmeter的函数助手,选择csvRead函数:app

 

 

其中:
CSV file to get values from | *alias:要读取的文件路径,为绝对路径 
CSV文件列号| next| *alias:从第几列开始读取,注意第一列是0框架

${__CSVRead(D:/test.csv,0)} 取到的值为grizz
${__CSVRead(D:/test.csv,1)} 取到的值为qq1111函数

3.Jmeter执行的时候,若是有多个线程,顺序读取每行的数据,若是线程组多于文件中的行数,则循环读取。如线程数为2,则第2个线程读取的是第二行的数据,线程数为3,线程数3大于文件中的行数2,则第3个线程读取的是第一行的数据。测试

PS:这一函数并不适合于读取很大的文件,由于整个文件都会被存储到内存之中。对于较大的文件,请使用配置元件CSV Data Set或者StringFromFile 。可是咱们不是压测,只是接口自动化,通常没有太大的数据文件,啊哈哈哈哈。

默认状况下,函数会在遇到的每个逗号处断行,须要换一个分隔符(经过设置属性csvread.delimiter来实现)
修改jmeter.properties文件:

#csvread.delimiter=,
修改成
csvread.delimiter=?

即把分隔符修改成?问号,注意前面的#号表明注释,要去掉。重启Jmeter生效。

二、使用的接口:

这里咱们以两个接口举例,其中Content-Type=application/json
1·获取token的接口/getToken
入参:
{
"flag":"test",
"appId":"001"
}
返回值:
{"returnFlag":"1000","returnMsg":"获取token成功","token":"19940622"}

2·使用token的接口/useToken,主要是测试useToken接口,useToken接口的token须要从getToken接口的返回值中取,其实就是参数关联。
入参:
{
"flag":"${token}",
"appId":"001"
}
返回值,以3个场景为例:
{"returnFlag":"1000","returnMsg":"使用token成功"}
{"returnFlag":"1001","returnMsg":"token为空"}
{"returnFlag":"1002","returnMsg":"token错误"}

对于接口的断言,咱们默认"returnFlag":"1000"即接口业务正常返回,1001,1002表明接口针对业务的不一样异常给予的返回,固然也在咱们的接口测试范围内。

咱们分v1,v2,v3,v4,4个版本按部就班的讲:

v1版本:

刚开始入门时,咱们的脚本可能会是这样的
先请求getToken接口,并获取token,用正则表达式提取以下

 

 

再请求useToken接口,flag的值输入${token},使用提取到的token值。
入参:{"flag":"${token}","appId":"001"}
返回值:{"returnFlag":"1000","returnMsg":"使用token成功"}
断言:"returnFlag":"1000",断言成功 

 

 

再测试后续两种场景,入参以下:
sampler-使用tokenv1-为空:
入参:{"flag":"","appId":"002"}
返回值:{"returnFlag":"1001","returnMsg":"token为空"}
断言:"returnFlag":"1000",断言失败

sampler-使用tokenv1-错误:
入参:{"flag":"errorToken","appId":"003"}
返回值:{"returnFlag":"1002","returnMsg":"token错误"}
断言:"returnFlag":"1000",断言失败
查看结果树

 

 

这一顿操做下来,没啥问题,由于useToken接口的3种场景咱们都覆盖了,只要把异常的场景的断言对应改一下,咱们的接口脚本就写好了,能够交付。可是若是咱们要实现接口自动化,那么v1版本中sampler的重复率比较高,咱们考虑能不能把useToken的3种场景放到一个sampler中。
因而有了v2版本

v2版本:

因为聪明的咱们有必定前瞻性,咱们知道,想下降脚本中sampler的重复率,须要借助数据文件,咱们能够把接口useToken的请求body直接从文件中读

入参1{"flag":"${token}","appId":"001"}
入参2{"flag":"","appId":"002"}
入参3{"flag":"errorToken","appId":"003"}

入参2,3咱们能够从文件中读取没问题,可是入参1这样从文件中读取,”${token}”读取出来的值就是字符串”${token}”,而不是咱们想要的”19940622”,怎么办呢?
因而咱们思考着,能够把接口useToken的请求分两种状况,须要正确的token和不须要正确的token,若是须要正确的token,咱们就在Jmeter中传给他,其它参数仍是能够在文本中读取;若是不须要正确的token,则请求所有从文件中读取。什么意思呢,继续往下看。

咱们设置存放csv文件的目录 DATA=/jmeter/testcase,由于咱们可能会屡次使用到这个目录,因此能够用${ DATA }表明咱们的文件目录。

useToken_v2.csv文件:

用例1-token正确?1?"appId":"001"}
用例2-token为空?0?{"flag":"","appId":"002"}
用例3-token错误?0?{"flag":"errorToken","appId":"003"}

举例:
${__CSVRead(${DATA}/useToken_v2.csv,1)}依次取到的是1,0,0
${__CSVRead(${DATA}/useToken_v2.csv,2)}第二次取到的是{"flag":"","appId":"002"}

若是咱们useToken_v2.csv文件有3行,则设置auto_interface_v2线程数为3。
由于对于csvRead函数,每个线程都有独立的内部指针指向文件数组中的当前行。当某个线程第一次引用文件时,函数会为线程在数组中分配下一个空闲行。如此一来,任何一个线程访问的文件行,都与其余线程不一样(除非线程数大于数组包含的行数)。
当咱们须要正确Token时,咱们利用if控制器,若是文本的第二列(我这里是以问号分隔的,由于默认是逗号,可是咱们接口json数据自己就有逗号,只能换一个)为1,则flag从Jmeter中本身读取;若是文本的第二列为0则表明不须要正确token,那接口入参咱们就所有从文件中读取。
If:${__CSVRead(${DATA}/useToken_v2.csv,1)}==1

 

 

sampler-使用tokenv2-haveToken的请求body为:

 

 

{"flag":"${token}", ${__CSVRead(${DATA}/useToken_v2.csv,2)}
即请求body由两部分组成,一部分来自Jmeter内,主要是获取正确的token,另外一部分来自useToken_v2.csv文件的第3列。
咱们这里token正确时,另外一个参数好像只有"appId":"001"这一种状况,显得好像这样写起来更加冗余,其实否则,咱们appId也有可能为空,也可能错误。这时候咱们的useToken_v2.csv文件应该会是这样:

用例1-token正确?1?"appId":"001"}
用例2-appId为空?1?"appId":""}
用例3-appId错误?1?"appId":"error appId "}
用例4-token为空?0?{"flag":"","appId":"002"}
用例5-token错误?0?{"flag":"errorToken","appId":"003"}

并且一个接口的入参不可能只有两个,但通常token只会有一个,因此当接口参数多时,这样写仍是减小了必定量的sampler的重复率。

If:${__CSVRead(${DATA}/useToken_v2.csv,1)}==0
请求body就为:${__CSVRead(${DATA}/useToken_v2.csv,2)}

 

 

再解释一下这里为什了加了一个计算器,和接口名称为何命名为使用tokenv2-noToken-${_number}。
首先,当咱们把线程数设置为3时,其实getToken接口也跑了3次,可是其实咱们只须要它跑一次,取出正确的token就能够了,getToken接口的if控制器跟计算器一块儿做用,当第1个线程启动时触发if控制器的规则${_number}==1,第2,第3个线程是就不会触发if控制器里面的getToken接口。

 

 

useToken接口命名后面加一个${_number},
使用tokenv2-noToken-${_number}和使用tokenv2-haveToken-${_number};主要是当接口报错时,能够根据接口的名称(其后面加了${_number},接口每一个场景${_number}都是不同的),判断其对应useToken_v2.csv文件的哪一行致使报错,可快速定位并进行报错修改。
方便理解,给出运行效果图以下:

 

 

v3版本:

v2版本咱们好像把咱们能作的都给作了,可是前提是
从文件中读取,”${token}”读取出来的值就是字符串”${token}”,而不是咱们想要的”19940622”!
随着时间的推移,楼主真的前先后后看过网上各类Jmeter教程和使用,不下40次,毕竟本身一直有信念,别人能作的本身也能够,1次看不懂的,那就看5次,5次还不懂,10次。不努力,没有办法比别人作得更好的。接着说随着时间的推移,grizz发现那个前提是能够打破的,由于Jmeter有个eval函数。
函数__eval能够用来执行一个字符串表达式,并返回执行结果。
举个栗子:
name=grizz
SQL=select * from able where name='${name}'
${ SQL }=select * from able where name='${name}'
${__eval(${SQL})}= select * from able where name='grizz'

如今咱们能够设置useToken_v3.csv文件以下:

用例1-token正确?{"flag":"${token}","appId":"001"}?"returnFlag":"1000"
用例2-token为空?{"flag":"","appId":"002"}?"returnFlag":"1000"
用例3-token错误?{"flag":"errorToken","appId":"003"}?"returnFlag":"1000"

明显的,咱们不须要if控制器来判断是否须要正确的Token了,脚本看起来清新了一些,并且咱们把响应断言也从文件中读取。这样开发修改接口的返回提示时,咱们能够直接经过修改csv文件完成对应的修改,不须要去动jmeter脚本。
入参:${__eval(${__CSVRead(${DATA}/useToken_v3.csv,1)})}
断言${__CSVRead(${DATA}/useToken_v3.csv,2)}

 

 

看到这咱们能够思考一下,在v4版本还有哪些内容能够优化?

v4版本:

v1到v2是入门到掌握,v2到v3应该是弱鸡到熟悉,v4应该到星耀了吧,很想写个v5版本,v5(威武),应该很强的怕。
咱们想一想,咱们的最终目标是实现写好接口自动化脚本后,接口变动的维护都只要操做csv文件。
那么当咱们的useToken接口新增了一个场景,token过时

useToken_v4.csv文件

用例个数?4
用例1-token正确?{"flag":"${token}","appId":"001"}?"returnFlag":"1000"
用例2-token为空?{"flag":"","appId":"002"}?"returnFlag":"1000"
用例3-token错误?{"flag":"errorToken","appId":"003"}?"returnFlag":"1000"
用例4-token过时?{"flag":"oldToken","appId":"003"}?"returnFlag":"1000"

设置线程组auto_interface_v4的线程数为
${__CSVRead(${DATA}/useToken_v4.csv,1)},其实就是文件useToken_v4.csv第一行的第二列,就是4

 

 

由于咱们的线程数与咱们的文件行数挂钩,但这里为何文件有5行,而线程数是4。前面说了,由于对于csvRead函数,每个线程都有独立的内部指针指向文件数组中的当前行。当某个线程第一次引用文件时,函数会为线程在数组中分配下一个空闲行。即Jmeter会分配一个线程去保存线程的属性,如线程数,启动时间,循环次数等。即当线程数设置为${__CSVRead(${DATA}/useToken_v4.csv,1)}时,控制线程属性的线程就读取了useToken_v4.csv文件的第一行。

PS:再说一下csvRead函数
csvRead函数默认从文件第一行开始读取,除非你二次开发,否则这个默认就一只在(苦笑),就是说咱们很差加文件列的标题(固然能够利用v4版本的第一行也行),下降了文件的可读性。但当对某个文件进行第一次读取时,文件将被打开并读取到一个内部数组中。若是在读取过程当中找到了空行,函数就认为到达文件末尾了,即容许拖尾注释。就是咱们能够在文件写完后回车一下,再写一些文件的注释,或者${__CSVRead(D:/test.csv,next)}了解一下,csvRead函数的第二个值为next可自行进行文件行标的切换。

其实当咱们的脚本量化后,还有不少东西要考虑的,接口csv文件的命名规范,线程组和sampler的命名规范,由于线程组和sampler的名称会在报告中体现,接口入口和出口标准,怎么维护咱们的数据和脚本更优雅,怎样更高效的运行脚本和生成更丰富的报告,更加的节省测试人力。

下一篇讲一下jemter和ant,jenkins的持续集成
Jmeter+ant+Jenkins 接口自动化框架完整版
接口汇总报告:

 

 

接口详细报告:

 

 

欢迎交流指正,感谢阅读。

相关文章
相关标签/搜索