jmeter+ant+jenkins+python接口自动化框架
背景
部分websocket请求+http请求,需要实现巡检自动报警,使用jmeter+ant+python+jenkins实现接口报错后通过python解析jmeter运行结果文件,有报错自动发送消息到企业微信,发送内容包括报错信息、用例名称,无报错不发送
websocket流程图


ant配置
- 将jmeter安装目录下 extras文件内的ant-jmeter-1.1.1.jar 复制到 ant lib/ 目录下
- 在ant安装目录下新建build.xml文件,参考如下文件内容修改:
<?xml version="1.0" encoding="UTF-8"?>
<project name="ant-jmeter-test" default="run" basedir=".">
<tstamp>
<format property="time" pattern="yyyy-MM-dd HH-mm" />
</tstamp>
<!-- 需要改成自己本地的 Jmeter 目录-->
<property name="jmeter.home" value="D:\apache-jmeter-4.0" />
<!-- jmeter生成jtl格式的结果报告的路径-->
<property name="jmeter.result.jtl.dir" value="D:\result-jtl" />
<!-- jmeter生成html格式的结果报告的路径-->
<property name="jmeter.result.html.dir" value="D:\result-report" />
<!-- 生成的报告的前缀-->
<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" />
<path id="xslt.classpath">
<fileset dir="${jmeter.home}/lib" includes="xalan*.jar"/>
<fileset dir="${jmeter.home}/lib" includes="serializer*.jar"/>
</path>
<target name="run">
<antcall target="test" />
<antcall target="report" />
</target>
<target name="test">
<taskdef name="jmeter" classname="org.programmerplanet.ant.taskdefs.jmeter.JMeterTask" />
<jmeter jmeterhome="${jmeter.home}" resultlog="${jmeter.result.jtlName}">
<!-- 声明要运行的脚本。"*.jmx"指包含此目录下的所有jmeter脚本-->
<testplans dir="" includes="*.jmx" />
</jmeter>
</target>
<target name="report">
<tstamp> <format property="report.datestamp" pattern="yyyy/MM/dd HH:mm" /></tstamp>
<xslt classpathref="xslt.classpath"
force="true"
in="${jmeter.result.jtlName}"
out="${jmeter.result.htmlName}"
style="${jmeter.home}/extras/jmeter-results-detail-report_21.xsl">
<param name="dateReport" expression="${report.datestamp}"/>
</xslt>
<copy todir="${jmeter.result.html.dir}">
<fileset dir="${jmeter.home}/extras">
<include name="collapse.png" />
<include name="expand.png" />
</fileset>
</copy>
</target>
</project>
jmeter配置
-
编辑jmeter jmeter.properties文件,把jmeter.save.saveservice.output_format=csv 值修改为xml,即:jmeter.save.saveservice.output_format=xml
-
jmeter.properties文件中,修改如下值为true
jmeter.save.saveservice.data_type=true jmeter.save.saveservice.label=true jmeter.save.saveservice.response_code=true # response_data is not currently supported for CSV output jmeter.save.saveservice.response_data=true # Save ResponseData for failed samples jmeter.save.saveservice.response_data.on_error=true jmeter.save.saveservice.response_message=true jmeter.save.saveservice.successful=true jmeter.save.saveservice.thread_name=true jmeter.save.saveservice.time=true jmeter.save.saveservice.subresults=true jmeter.save.saveservice.assertions=true jmeter.save.saveservice.latency=true jmeter.save.saveservice.connect_time=true jmeter.save.saveservice.samplerData=true jmeter.save.saveservice.responseHeaders=true jmeter.save.saveservice.requestHeaders=true jmeter.save.saveservice.encoding=true jmeter.save.saveservice.bytes=true # Only available with HttpClient4 jmeter.save.saveservice.sent_bytes=true jmeter.save.saveservice.url=true jmeter.save.saveservice.filename=true jmeter.save.saveservice.hostname=true jmeter.save.saveservice.thread_counts=true jmeter.save.saveservice.sample_count=true jmeter.save.saveservice.idle_time=true
jenkins安装插件
- Ant In Workspace
- Groovy Postbuild
- HTML Publisher
- Localization: Chinese (Simplified)
默认构建
指定构建文件

报告样式
-
System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "")

html报告配置

自动发送报错信息python脚本
# coding=utf-8
from xml.dom.minidom import parse
import xml.dom.minidom
import os
import time
import requests
import json
def get_jtl():
jtl_path = '/home/run_jmx_online/jtl/'
dir_list = os.listdir(jtl_path)
if not dir_list:
return
else:
dir_list = sorted(dir_list,key=lambda x: os.path.getmtime(os.path.join(jtl_path, x)))
print(' jtl文件列表: ' + str(dir_list))
jtl_file = jtl_path + ''.join(dir_list[-1])
print(' 读取文件: ' + jtl_file)
return jtl_file
def read_xml():
error_msg_list = []
dom_tree = xml.dom.minidom.parse(get_jtl())
collection = dom_tree.documentElement
httpSample = collection.getElementsByTagName('httpSample')
sample = collection.getElementsByTagName('sample')
judge = {'true' : 0, 'error' : 0}
for each_httpSample in httpSample:
if each_httpSample.hasAttribute('s'):
if each_httpSample.getAttribute('s') == 'true':
judge['true'] += 1
if each_httpSample.getAttribute('s') == 'false' and each_httpSample.getAttribute('rc') != '401' and each_httpSample.getAttribute('rm') != 'Sampler configured for using existing connection, but there is no connection':
judge['error'] += 1
httpSample_error_title = each_httpSample.getAttribute('lb')
httpSample_error_failure_message = each_httpSample.getElementsByTagName('failureMessage')[0].childNodes[0].nodeValue
httpSample_error_response_data = each_httpSample.getElementsByTagName('responseData')[0].childNodes[0].nodeValue
error_msg_list.append({'请求标题' : httpSample_error_title,
'断言失败信息' : httpSample_error_failure_message,
'实际返回信息' : httpSample_error_response_data})
for each_sample in sample:
if each_sample.hasAttribute('s'):
if each_sample.getAttribute('s') == 'true':
judge['true'] += 1
if each_sample.getAttribute('s') == 'false' and each_sample.getAttribute('rc') != '401' and each_sample.getAttribute('rm') != 'Sampler configured for using existing connection, but there is no connection' and each_sample.getAttribute('rc') != 'Websocket I/O error':
judge['error'] += 1
sample_error_title = each_sample.getAttribute('lb')
# sample_error_failure_message = each_sample.getElementsByTagName('failureMessage')[0].childNodes[0].nodeValue
sample_error_response_data = each_sample.getElementsByTagName('responseData')[0].childNodes[0].nodeValue
error_msg_list.append({'请求标题' : sample_error_title,
# '断言失败信息' : sample_error_failure_message,
'实际返回信息' : sample_error_response_data})
print('正确请求数: ' + str(judge['true']))
print('错误请求数: ' + str(judge['error']))
print('错误请求: ' + str(error_msg_list))
return {'judge' : judge, 'error_msg_list' : error_msg_list}
def result_inform():
now_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
if read_xml()['judge']['error'] == 0:
pass
else:
result_true = read_xml()['judge']['true']
result_error = read_xml()['judge']['error']
all_result = result_true + result_error
error_msg_list = read_xml()['error_msg_list']
url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send'
headers = {"Content-Type": "text/plain"}
data = {
"msgtype": "text",
"text": {
"content": now_time + '\n\n' +
'线上环境\n\n' +
'请求总数: ' + str(all_result) + '\n\n' +
'正确请求数: ' + str(result_true) + '\n\n' +
'错误请求数: ' + str(result_error) + '\n\n' +
'错误请求信息: ' + str(error_msg_list) + '\n\n' +
'内网测试报告: ' + 'http:///job/ims_test_report_online/ims-test-report-online/' + '\n'
}
}
r = requests.post(url, headers=headers, json=data)
print('发送信息:{}'.format(data))
if __name__ == '__main__':
result_inform()
删除历史报告文件脚本
- 每次都会保留最近三次的报告文件,其他的删除
import os
def del_jtl(path):
dir_list = os.listdir(path)
if not dir_list:
return
else:
dir_list = sorted(dir_list,key=lambda x: os.path.getmtime(os.path.join(path, x)))
del_jtl = dir_list[0:-3]
print(' jtl 文件列表: ' + str(dir_list))
# print(del_jtl)
for i in del_jtl:
os.remove(path + ''.join(i))
print('删除 jtl 文件: ' + path + ''.join(i))
def del_html(path):
dir_list = os.listdir(path)
if not dir_list:
return
else:
dir_list = sorted(dir_list,key=lambda x: os.path.getmtime(os.path.join(path, x)))
del_jtl = dir_list[3:-3]
print(' html 文件列表: ' + str(dir_list))
print(del_jtl)
for i in del_jtl:
os.remove(path + ''.join(i))
print('删除 html 文件: ' + path + ''.join(i))
del_jtl('/home/run_jmx_online/jtl/')
del_html('/home/run_jmx_online/html/')
del_jtl('/home/run_jmx_standby/jtl/')
del_html('/home/run_jmx_standby/html/')
报告内容


浙公网安备 33010602011771号