接口测试 基于 jmeter 和 shell 的接口性能自动化

bocy · March 01, 2016 · Last by 菠萝蜜 replied at June 15, 2017 · 6920 hits
本帖已被设为精华帖!

基于jmeter和shell的接口性能自动化

1. 总体需求

由于性能测试中涉及的查询接口多,版本迭代频繁,版本更新后自动跑一轮查询业务的性能,可以及时发现一些开发修复bug触发的非预期的bug,利用晚上时间快速重测性能接口可以解放人工测试的时间,让测试人员可以把精力集中在测试复杂接口,调优分析性能瓶颈上。

2. 实现流程

自动化的场景模拟真实手工测试,操作步骤和手工测试一样。

3.准备工作

准备软件:

系统环境:CentOS release 6.7 (Final)
内核版本:Linux localhost 2.6.32-573.7.1.el6.x86_64
测试工具:apache-jmeter-2.13 http://jmeter.apache.org/download_jmeter.cgi
运行JDK环境:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
python环境:Python 2.6.6
服务器监控nmon:http://nmon.sourceforge.net/pmwiki.php?n=Site.Download

4.具体实现

4.1用例执行

Jmeter的执行支持命令行的方式执行,并且会有一个粗略报告,我们取这个报告里面的执行结果并保存下来
Jmeter的执行结果:


root@localhost bin]# ./jmeter -n -t singin.jmx
Creating summariser <summary>
Created the tree successfully using singin.jmx
Starting the test @ Mon Dec 14 16:42:33 CST 2015 (1450082553651)
Waiting for possible shutdown message on port 4445
summary + 1 in 1s = 1.3/s Avg: 268 Min: 268 Max: 268 Err: 0 (0.00%) Active: 1 Started: 1 Finished: 0
summary + 2 in 0s = 50.0/s Avg: 17 Min: 14 Max: 20 Err: 0 (0.00%) Active: 0 Started: 1 Finished: 1
summary = 3 in 1s = 3.7/s Avg: 100 Min: 14 Max: 268 Err: 0 (0.00%)
Tidying up ... @ Mon Dec 14 16:42:34 CST 2015 (1450082554551)
... end of run

由sumary统计行可以得到我们需要收集的测试结果:

主流程脚本
#/bin/bash

source /etc/profile
Jmeter_Home='/usr/local/apache-jmeter-2.13'
TestReport='/data/loadtest/report'
LogDIR='/data/loadtest/log'
Date=`date +"%F"`
cd /data/loadtest/
>summary.txt
#清理上次执行结果
run_test()
{
#获取测试用例
for i in `find ./testcase/ -name *.jmx|awk -F '.' '{print $2}'`
do
casename=`echo "$i"|awk -F '/' '{print $4}'`
>log/${casename}.txt
echo -n "$i ">>summary.txt
#发起监控
./monitor.sh >/dev/null 2>&1 &
#开始执行测试
$Jmeter_Home/bin/jmeter -n -t /data/loadtest${i}.jmx >>log/${casename}.txt &
sleep 310
#如果执行310s还未结束,强制终止执行
ps -ef | grep java |grep -v grep | awk '{print $2}' |xargs kill -9
sleep 3
#提取结果
grep 'summary =' log/${casename}.txt| tail -1 |awk -F '[\t / (]+' '{if($7>10000 && $17<10.00){printf("%s %d %d %d %.2f% pass ",$7,$10,$3,$16,100-$17)}else{printf("%s %d %d %d %.2f%% fail ",$7,$10,$3,$16,100-$17)}}'>>summary.txt
cat monitor.txt >>summary.txt
echo '' >> summary.txt
#获取关键日志
ssh 10.1.30.54 'tail -n 300 /data/logs/fcuh-user/catalina.out'>${LogDIR}${i}.log
done
}
run_test
sleep 3
#生成html报告
sh genHTML.sh
sleep 1
#发送邮件
python sendmail.py

4.2服务器监控

服务器资源收集方面,选用的是nmon监控工具,因为它可以后台收集结果保存到文件。由于每个用例测试5分钟,所以只需要监控300秒,每5s监控一次,对应命令:

nmon -f -t -s5 -c60 -F /data/test.nmon

测试用例跑完再读取这个结果文件,获取有用的信息
当前只统计了磁盘io和cpu的占用率信息,原始文件保存在本地目录,如果需要,可以手动查看到。

监控脚本:
#!/bin/bash
#读取监控服务器列表
SERVERLIST=`cat serverlist`
DATE=`date +'%F'`
mkdir -p /data/loadtest/monitor/$DATE
TIME=`date +'%T'`
#发起监控
for i in $SERVERLIST
do
ssh $i 'nmon -f -t -s5 -c60 -F /data/test.nmon >/dev/null 2>&1 &'
done
#监控5分钟
sleep 303
>monitor.txt
#收集监控结果,保存到monitor.txt
for i in $SERVERLIST
do
scp $i:/data/test.nmon /data/loadtest/monitor/$DATE/${i}_${TIME}.nmon
io=`cat /data/loadtest/monitor/$DATE/${i}_${TIME}.nmon|grep "DISKBUSY,T" | awk -F ',' '{sum+=$3} END {printf("%.2f%",sum/NR)}'`
cpu=`cat /data/loadtest/monitor/$DATE/${i}_${TIME}.nmon|grep "CPU_ALL,T" | awk -F ',' '{sum+=$6} END {printf("%.2f%",100-sum/NR)}'`
#net=`cat /data/loadtest/monitor/$DATE/${i}_${TIME}.nmon|grep "NET,T"|awk -F ',' '{sum_r+=$4}{sum_w+=$6} END {print sum_r/NR,sum_w/NR}'`
echo -n "${cpu} ${io} ">>monitor.txt
done

将用例执行结果和监控结果都汇总到summary.txt里
生成的格式如下,方便后面生成html格式的报告

[root@localhost loadtest]# cat summary.txt 
/testcase/user/获取用户自己的信息 10748.6 8 3225296 0 100.00% pass 32.87% 5.34% 81.59% 2.12% 0.15% 1.78% 12.13% 32.83% 17.18% 6.14%
/testcase/user/未读消息数 11487.4 7 3446960 0 100.00% pass 32.33% 12.57% 69.02% 1.86% 0.18% 1.74% 13.55% 35.85% 18.56% 7.65%
服务器监控脚本
#!/bin/bash

SERVERLIST=`cat serverlist`
DATE=`date +'%F'`
mkdir -p /data/loadtest/monitor/$DATE
TIME=`date +'%T'`
for i in $SERVERLIST
do
ssh $i 'nmon -f -t -s5 -c60 -F /data/test.nmon >/dev/null 2>&1 &'
done

sleep 303
>monitor.txt
for i in $SERVERLIST
do
scp $i:/data/test.nmon /data/loadtest/monitor/$DATE/${i}_${TIME}.nmon
io=`cat /data/loadtest/monitor/$DATE/${i}_${TIME}.nmon|grep "DISKBUSY,T" | awk -F ',' '{sum+=$3} END {printf("%.2f%",sum/NR)}'`
cpu=`cat /data/loadtest/monitor/$DATE/${i}_${TIME}.nmon|grep "CPU_ALL,T" | awk -F ',' '{sum+=$6} END {printf("%.2f%",100-sum/NR)}'`
echo -n "${cpu} ${io} ">>monitor.txt
done

4.3生成html报告

#!/bin/sh
>index.html
echo "<html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/><title>用户端自动化性能测试报告</title>">>index.html
echo `cat style.css`>>index.html
(
cat <<EOF
<script language="JavaScript">
function show_detail(detail){
if(detail.style.display=="none"){
detail.style.display="";
}
else{
detail.style.display="none";
}
}
</script>
EOF
)>>index.html
echo "</head><body><h1>用户端自动化性能测试报告</h1><hr size="1">">>index.html
sum=`cat summary.txt | wc -l`
sucess=`cat summary.txt|grep pass |grep -v grep|wc -l`
fail=`expr $sum - $sucess`
rate=`echo "$sucess $sum"|awk '{printf("%.2f%%",$1/$2*100)}'`
(
cat <<EOF
<table><tr><td>
<h2>结果汇总</h2>
<table width="60%" cellspacing="2" cellpadding="5" border="0" class="details" align="left">
<tr><th>总接口数</th><th>成功接口数</th><th>失败接口数</th><th>测试通过率</th></tr>
<tr align="center"><td>$sum</td><td>$sucess</td><td>$fail</td><td>$rate</td></tr>
</tr></table>
</td></tr>
EOF
)>>index.html
(
cat <<EOF
<tr><td>
<h2>概要结果</h2>
<table width="95%" cellspacing="2" cellpadding="5" border="0" class="details" align="left">
<tr valign="top">
<th>测试接口</th><th>每秒请求数(tps)</th><th>平均响应时间(ms)</th><th>总事务数</th><th>失败事务数</th><th>事务成功率</th><th>测试结果</th>
</tr>
<tr valign="top" class="">
EOF
)>>index.html
cat summary.txt |while read line
do
echo $line | awk '{if($7=="pass"){print "<tr><td>"$1"</td><td>"$2"</td><td>"$3"</td><td>"$4"</td><td>"$5"</td><td>"$6"</td><td class=\"Pass\">"$7"</td></tr>"}else{print "<tr><td>"$1"</td><td>"$2"</td><td>"$3"</td><td>"$4"</td><td>"$5"</td><td>"$6"</td><td class=\"Failure\">"$7"</td></tr>"}}'>>index.html
done
echo "</tr></table></td></tr>">>index.html
echo "<table><tr><td><font color="red"><b>测试结果pass标准:tps>10000且事务成功率>90%</b></font><td></tr><tr><td><h2><a href=\"javascript:show_detail(detail)\">详细结果查看附件</a></h2></td></tr></table>">>index.html
#echo "<div class=\"page_details_expanded\" id=\"detail\" style=\"display:none;\" width=\"95%\">">>index.html
(
cat <<EOF
<table width="95%" cellspacing="2" cellpadding="5" border="0" class="details" align="left" id="detail" style="display:none">
<tr valign="top">
<th>测试接口</th><th>每秒请求数tps</th><th>平均响应时间(ms)</th><th>总事务数</th><th>失败事务数</th><th>成功率</th><th>测试结果</th><th>nginx服务器cpu</th><th>nginx服务器io</th><th>web服务器cpu</th><th>web服务器io</th><th>service服务器cpu</th><th>service服务器io</th><th>主数据库服务器cpu</th><th>主数据库服务器io</th><th>从数据库服务器cpu</th><th>从数据库服务器io</th>
</tr>
<tr valign="top" class="">
EOF
)>>index.html
j=1
for i in `cat summary.txt`
do
if [ `expr $j % 17 ` != 0 ]; then
echo '<td align="left">'$i'</td>'>>index.html
else
echo '<td align="left">'$i'</td></tr>'>>index.html
fi
j=`expr $j + 1`
done
echo "</tr></table></td></tr></table></body></html>">>index.html
html样式表
<style type="text/css">
body {
font:normal 68% verdana,arial,helvetica;
color:#000000;
}
table tr td, table tr th {
font-size: 78%;
}
table.details tr th{
color: #ffffff;
font-weight: bold;
text-align:center;
background:#2674a6;
white-space: nowrap;
}
table.details tr td{
background:#eeeee0;
white-space: nowrap;
}
h1 {
margin: 0px 0px 5px; font: 265% verdana,arial,helvetica
}
h2 {
margin-top: 1em; margin-bottom: 0.5em; font: bold 185% verdana,arial,helvetica
}
h3 {
margin-bottom: 0.5em; font: bold 115% verdana,arial,helvetica
}
.Failure {
font-weight:bold; color:red;
}
.Pass {
font-weight:bold; color:green;
}
</style>

4.4发送测试结果邮件

发邮件脚本
#!/usr/bin/env python
#coding: utf-8
import string
import smtplib
import os
import datetime
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.header import Header

today = datetime.date.today()
sender = 'xx@xx.com'
receiverlist = ["a@a.com","b@b.com","c@c.com"]
subject = '%s %s' % ('用户端自动化性能测试报告',today)
smtpserver = 'smtp.exmail.qq.com'
username = 'xx@xx.com'
password = 'xxx'
f = open('index.html',"r")
content = f.read()

#msg = MIMEText(content,'html','utf-8')
msg = MIMEMultipart()
msg.attach(MIMEText(content,'html','utf-8'))

msg['From'] = 'xx@xx.com'
msg['to'] = ','.join(receiverlist)
msg['Subject'] = subject

att=MIMEText(open('index.html','rb').read(),'base64','gb2312')
att["Conten-Type"]='application/octet-stream'
att["Content-Disposition"]='attachment;filename="Load test result.html"'
msg.attach(att)

smtp = smtplib.SMTP()
smtp.connect(smtpserver)
smtp.ehlo()
smtp.starttls()
smtp.ehlo()
#smtp.set_debuglevel(1)
smtp.login(username, password)
smtp.sendmail(msg['From'],msg['to'],msg.as_string())
smtp.quit()

测试结果截图:


这里还涉及到ssh免密码登录、jmeter测试用例的编写,没有细说,其实百度一下就知道了,搜索不到的朋友可以私信我。

共收到 55 条回复 时间 点赞

这个好

很好的文章

楼主你好,我有几个问题想请教下:
1、这里调用的jmeter测试用例是jmeter里导出的jmx文件么?
2、要自动化执行的话,这些脚本是不是也有一个配置文件一样的来处理脚本的执行顺序?还是直接执行主流程脚本的sh就可以了?
3、这个脚本是跑在一个单独的服务器上么?需要什么配置么?
4、然后这个测试脚本的执行时间如何设置?还是按次数来跑?

我现在公司的接口测试很简单,就用下postman把新接口调通,返回数据正常就没在做别的了,也没有对所有的接口进行回归,所以我也想试试自动化这些,还希望楼主多多指教,谢谢~

棒~目前正在考虑自动化性能,感谢分享

很不错

bocy #6 · March 02, 2016 作者

#3楼 @shadow000902
1、这里调用的jmeter测试用例是jmeter里导出的jmx文件么?
// 是的,用jmeter GUI生成的用例就行

2、要自动化执行的话,这些脚本是不是也有一个配置文件一样的来处理脚本的执行顺序?还是直接执行主流程脚本的sh就可以了?

// 直接执行主流程脚本就行,脚本会去搜索指定目录下的用例,在主流程脚本中指定。我这里是:/data/loadtest/testcase/
就是这个语句:

for i in `find ./testcase/ -name *.jmx|awk -F '.' '{print $2}'`

3、这个脚本是跑在一个单独的服务器上么?需要什么配置么?
// 是在单独机器上跑,系统环境是centos 6.7的,需要配置的环境就是设置JDK和安装python

4、然后这个测试脚本的执行时间如何设置?还是按次数来跑?
// 脚本中设置的是跑5分钟,脚本中也做了判断,如果5分钟还没跑完会杀掉,不影响结果
我的jmeter设置是这样的

#6楼 @bocycn 好的,谢谢楼主详细的回答,我今天就找时间去实践一下,有问题再来向您请教

楼主头像的那位女生很性感啊~ Alexandra Anna Daddario~~看过True Detective第一季的都直道!

很不错的文章,收藏

多谢楼主的文章分享

写的很好。用nmon监控服务器可以把数据保存成文件,分析数据的时候就很方便了。整个测试流程跑下来要1个半小时多,放在晚上跑充分利用时间。楼主在细节方面很用心啊。

赞一个。不过我看有事物失败的情况下,成功率怎么还是100%?

厉害

bocy #14 · March 03, 2016 作者

#8楼 @ansonwoo 我是看『末日坍塌』才知道她的,身材一级棒,看来你才是真『奶粉』😄

bocy #15 · March 03, 2016 作者

#12楼 @mark 因为相对于总数280万请求,失败的78,在保持两位小数的情况下就被忽略了😄

#14楼 @bocycn 男的看到她 都会变成奶粉.

一个jmx文件只包含一个接口吗?

bocy #18 · March 04, 2016 作者

#17楼 @mark 是的

一个jmx文件只包含一个接口,通用部分/复用部分可以用 Include Controller ,挺好的办法。以前的想法是直接通过 Threads 来管理 Cases ,看来还不如独立开来分文件更灵活。

楼主太厉害了。。。

SERVERLIST=cat serverlist
问下这个serverlist文件是怎么生成的?里面是啥内容呢?
@bocycn

这分享真棒

bocy #23 · March 04, 2016 作者

#21楼 @mark 里面直接写上各个服务器的ip地址,格式像这样:

-bash-4.1$ cat serverlist 
10.1.1.53
10.1.1.54
10.1.1.55
10.1.1.235
10.1.1.52

#23楼 @bocycn 如果被施压的接口部署在一台机器上,那就只需要填一个server地址就行吧

#6楼 @bocycn 楼主,还有两个问题请教:
1、如果我在接口请求中进行了参数化,那这个用例导入测试服务器的话就没法用了,这个有什么办法处理呢?
2、文件调用的时候会出错,请问下你的各个脚本文件是如何放置的?

find: ./testcase/: No such file or directory
sh: genHTML.sh: No such file or directory
python: can't open file 'sendmail.py': [Errno 2] No such file or directory

我不是学计算机的,所以各个语言都不太会,也在努力的熟悉python、shell等语言的基础,问题可能有点菜,还请见谅~~~

bocy #26 · March 05, 2016 作者

#25楼 @shadow000902

1、如果我在接口请求中进行了参数化,那这个用例导入测试服务器的话就没法用了,这个有什么办法处理呢?

参数化,照样可以用,如果你的参数化是文件的话,就用相对路径,比如你的脚本在/data/loadtest/testcase/xxx.jmx,参数文件也放到这个目录下,比如是xxx.dat调用的时候直接写xxx.dat就行

2、文件调用的时候会出错,请问下你的各个脚本文件是如何放置的?

文件路径是这样的:
脚本都放在/data/loadtest/
测试用例testcase目录也在这个目录下,所以用例目录的绝对路径是/data/loadtest/testcase/

bocy #27 · March 05, 2016 作者

#24楼 @mark 环境中服务器只有一台服务器的话,就只监控一台服务器,就用一个,这里填的就是需要监控的服务器的ip地址

#26楼 @bocycn 楼主,接口在Jmeter里编辑效率好低啊,有没有简便一点的方法呢,想问下您是怎么做的呢?

bocy #29 · March 06, 2016 作者

#28楼 @shadow000902 我是在jmeter GUI里编辑的,因为每个接口都要试跑一下,用这个还是挺方便的。当然我是直接把调试成功的接口另存为新的接口用例,再稍微修改一下地址和参数就行,编辑效率也不会特别低吧。想过做个web管理用例的界面,但是对前端技术不熟悉,短时间搞不定,这个也是后续可以搞的一个方向,如果搞好了从web页面编辑和执行测试用例,就是个自动化测试平台了吧

@bocycn

问下下面的这个命令是做什么用的?
ssh 10.1.30.54 'tail -n 300 /data/logs/fcuh-user/catalina.out'>${LogDIR}${i}.log

bocy #31 · March 08, 2016 作者

#30楼 @mark 这个是打印tomcat应用日志的,如果接口性能有问题,可以直接看到当时错误日志中的最后300行,方便定位接口是否有报错

#31楼 @bocycn 多谢楼主的回答,还有个问题

我跑完后,有些服务器是没有数据,请问下这些数据是怎么能获取到呢?我这边的接口服务用的是nginx(web服务器) + web.py(web框架) + uwsgi(应用服务器),应该是部署到一台机器上了

楼主你好,不知能否可以提供一下所有脚本的脚本英文名?如果能有一个压缩包统一提供所有脚本进行学习那就更好了:P先行谢过

#30楼 @mark 读取接口所在服务器的日志,不过每次都会需要输入该服务器的密码~有点麻烦,我问我公司服务端开发说是可以做一个证书来通过这个输入密码的步骤,这样就不用每次都输入密码了。。。

#34楼 @shadow000902 百度搜索下ssh免密码登录就可以自己解决这个问题

shell承担的业务太多,应该改成主要用python实现,维护成本和代码量会大减,尤其是html这部分

bocy #37 · March 10, 2016 作者

#32楼 @mark 一台服务器就只监控一台服务器性能数据呗,可以把其他的列去掉,修改一下生成html的脚本

bocy #38 · March 10, 2016 作者

#36楼 @liugee 是的,用python可能更合适,我是对shell更熟悉一点,首先想到shell实现了。代码还有很多改进的地方,这里只是分享一下思路,大家可以根据自己的项目情况和自己擅长的语言来实现

楼主,我试了一下,发现接口失败率有点高。。。经常99%之类的,但是又没有异常,响应时间也在10ms以内,这个可能会是啥原因呢。。。

#39楼 @glimjoe 你看一下楼主的评判成功失败的有一大部分是按tps(每秒的请求数)来判的,你线程数加高一点就可以更大的接近10000这个数,不过我主要是用作接口遍历,性能只是随便看一下,那只要看事务成功率就可以了

#40楼 @shadow000902 。。。我是说失败率,不是测试结果,tps倒不是主要考虑的问题。。。

bocy #42 · March 16, 2016 作者

#39楼 @glimjoe 事务成功率和你设置的断言有关,检查一下你的jmeter用例中断言判断

bocy #43 · March 16, 2016 作者

#33楼 @ebuluo
测试脚本和测试用例,都在/data/loadtest/目录下,主要文件和作用说明如下:
.
├── genHTML.sh #生成HTML文件脚本
├── index.html #生成的HTML测试报告
├── jmeter.log #jmeter执行生成的日志文件
├── loadtest.sh #测试的主流程脚本
├── log #存放执行日志的目录
├── monitor #存放监控结果文件的目录
├── monitor.sh #监控脚本
├── monitor.txt #监控记录汇总
├── sendmail.py #发送邮件脚本
├── serverlist #监控服务器列表文件
├── style.css #测试报告样式表
├── summary.txt #测试汇总报告
├── testcase #测试用例目录
└── testResult.txt #测试结果临时文件

脚本执行流程:loadtest.sh ->monitor.sh-> genHTML.sh-> sendmail.py

楼主勾搭我一下 QQ:49875183 微信:xiaozhao129540

楼主你好,全部是Linux跑的?监控也在吗?

bocy #46 · April 12, 2016 作者

好棒马克一下!

楼主可以加我一下QQ么:1060261630

很棒,感谢分享

@bocycn 楼主我想问一下,如果使用JM做接口测试,接口返回数据,如何连接数据库,进行数据正确性的验证呢?

#3楼 @shadow000902 你可以通过JM,结合ant ,通过配置buil.xml生成测试报告,然后全部集成到Jenkins里面,持续集成自动化测试,方便接口回归,挺方便的

很好的文章

收藏了。启发很大

Nmon报告解析可以pyNmonAnalyzer类库,会将nmon文件生成HTML图表

强大,顶一个

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up