基于Python组装jmx并调用JMeter实现压力测试
JMeter可以通过os命令调用Python脚本,Python同样可以通过系统命令调用JMeter执行压测
Python调用JMeter
首先要安装JMeter,官方下载地址
解压并配置配置环境路径或建立软连,使得在命令输入jmeter便可以执行,如
unzipapache-jmeter-5.3.zip
mvapache-jmeter-5.3/usr/loca/jmeter
ln-s/usr/local/jmeter/bin/jmeter/usr/bin/jmeter
ln-s/usr/local/jmeter/bin/jmeter-server/usr/bin/jmeter-server
打开JMeter并设计一个测试计划保存为testplan.jmx
使用Python调用JMeter压测并生成报告
Python中可以使用os.system()或supprocess.Popen()调用系统命令,前者实时显示在屏幕上,后者可以获取到屏幕输出信息。
使用Python调用JMeter运行及生成报告的命令如下。
importsubprocess jmx_file='testplan.jmx'#jmx文件路径 result_file='result.jtl'# log_file='run.log' report_dir='report' run_cmd=f'jmeter-n-t{jmx_file}-l{result_file}-j{log_file}'#无界面运行JMeter压测命令 report_cmd=f'jmeter-g{result_file}-o{report_dir}'#生成HTML报告命令 #不需要获取屏幕输出是,可以使用os.system() #os.system(run_cmd) #os.system(report_cmd) #需要获取屏幕输出是,可以使用subprocess.Popen() p1=subprocess.Popen(run_cmd,shell=True,stdout=subprocess.PIPE) print(p1.stdout.read().decode('utf-8')) p2=subprocess.Popen(report_cmd,shell=True,stdout=subprocess.PIPE) print(p2.stdout.read().decode('utf-8'))
组装jmx
每一测试计划为一个jmx文件,jmx实际上是xml格式的,包含一些JMeter自定义的格式规范。
常用的组件有:
- :测试计划
- :线程组
- :CSV数据文件
- :HTTP请求
- :HTTP请求头管理器
- :Cookies管理器
- :DNS缓存管理器
- :监听器(包括查看结果树、聚合报告等)
- :响应断言
:三方Dubbo请求插件
Dubbo插件jmeter-plugins-dubbo下载链接
jmx中,如果一个组件有子组件,格式为
...线程组配置 ...内部子组件 ··· 如果不包含子组件,则后面接一个单标签直接结束,例如: ```xml ...CSV数据组件配置
详细的格式可以自己使用JMeter创建一个测试计划,使用文本文件打开jmx文件查看。
使用Python组装jmx文件的方式有两种,一种是固定模板的数据渲染,一种类似JMeter的逐个组件添加,第一种比较简单。
通过分析jmx文件中的变量,我们使用jinja2模板语法,将其中的变量进行参数化,对需要判断和循环的变量设置if和for循环。
Jinja2中文文档
假设我们的测试计划结构为:
测试计划 DNS缓存管理器 Cookies管理器 CSV文件(多个) ... 聚合报告 线程组(多个) CSV文件(多个) HTTP请求(或Dubbo请求) HTTP请求头管理器 CSV文件(多个) 响应断言 察看结果树
将jmx中的关键数据抽取并组合,我使用的完整数据格式如下:
data.yaml
test_plan_name:测试计划 comments:测试计划描述 hosts: -name:las.secoo.com address:112.126.120.128 cookies: clear_each_iteration:'true' csv_files: -{'name':'数据文件1','path':'data.csv','varnames':'a,b','delimiter':','} -{'name':'数据文件2','path':'data.csv','varnames':'c,d','delimiter':','} thread_groups: -thread_group_name:线程组1 comments:线程组1描述 enabled:'true' num_threads:50 loops:-1 ramp_time:0 scheduler:'true' duration:30 delay:'' http_samples: -request_name:HTTP-GET enabled:'true' csv_files: -{'name':'数据文件4','path':'data.csv','varnames':'a,b','delimiter':','} request: protocol:https domain:httpbin.org port:'' encoding:'' path:/get method:GET connect_timeout:'' response_timeout:'' params:{'a':1,'b':2} headers:{'token':'aaaaaa','x-text':'bbbbb'} follow_redirects:'false' auto_redirects:'false' use_keepalive:'false' validate: -test_field:response_data#response_data响应文本response_code响应代码response_message响应信息response_headers #请求头request_headerssample_labelURL样本response_data_as_document文档(文本)请求数据request_data test_type:2#2包括1匹配8相等16字符串否+4或者+32 strings:['a','b'] -request_name:HTTP-POST enabled:'true' request: protocol:https domain:httpbin.org port:'' encoding:'' path:/post method:POST data:{'c':3,'d':4} follow_redirects:'false' auto_redirects:'false' use_keepalive:'false' connect_timeout:'' response_timeout:'' -request_name:HTTP-JSON enabled:'true' request: protocol:https domain:httpbin.org port:'' encoding:'' path:/post method:POST connect_timeout:'' response_timeout:'' raw_data:'{"e":5,"f":6}' follow_redirects:'false' auto_redirects:'false' use_keepalive:'false' -thread_group_name:线程组2 comments:线程组2描述 enabled:'false' num_threads:50 loops:-1 ramp_time:0 scheduler:'true' duration:30 delay:'' csv_files: -{'name':'数据文件3','path':'data.csv','varnames':'a,b','delimiter':'\t'} dubbo_samples: -request_name:查询运费接口-dubbo enabled:'true' registry: type:zookeeper group:'' address:'zk-mall1.secoolocal.com:5181?backup=zk-mall2.secoolocal.com:5181,zk-mall3.secoolocal.com:5181' dubbo: timeout:100 retires:0 group:'' connections:100 load_balance:random cluster:failfast service:'com.secoo.business.config.rpc.service.BusinessConfigStoreService' method:queryFreight headers: Content-Type:'application/json' params: -type:java.util.List value:${freight_wareHouseId_sendAreaId} -type:java.lang.String value:110100 -type:java.util.List value:${freight_wareHouseId_sendAreaId} validate: -test_field:response_data#response_data响应文本response_code响应代码response_message响应信息response_headers test_type:16#2包括1匹配8相等16字符串否+4或者+32 strings:['"code":0']
对应的模板文件tpl.xml代码如下:
{{comments}} false true false {%ifhosts%} {%forhostinhosts%} {{host.name}} {{host.address}} {%endfor%} false true {%endif%}{%ifcookies%} {{cookies.clear_each_iteration}} {%endif%}{%ifcsv_files%}{%forcsv_fileincsv_files%} dat/{{csv_file.path}} UTF-8 {{csv_file.varnames}} true {{csv_file.delimiter}} false true false shareMode.group {%endfor%}{%endif%} false saveConfig true true true true
true true true false true true false false false true false false true true 0 true true true true {%forthread_groupinthread_groups%} {{thread_group.comments}} continue false {{thread_group.loops}} {{thread_group.num_threads}} {{thread_group.ramp_time}} {{thread_group.scheduler}} {{thread_group.duration}} {{thread_group.delay}} false {%ifthread_group.csv_files%}{%forcsv_fileinthread_group.csv_files%} dat/{{csv_file.path}} UTF-8 {{csv_file.varnames}} true {{csv_file.delimiter}} false true false shareMode.group {%endfor%}{%endif%}{%ifthread_group.http_samples%}{%forhttp_sampleinthread_group.http_samples%} {%ifhttp_sample.request.params%}{%forname,valueinhttp_sample.request.params.items()%} false {{value}} = true {{name}} {%endfor%}{%endif%}{%ifhttp_sample.request.data%}{%forname,valueinhttp_sample.request.data.items()%} false {{value}} = true {{name}} {%endfor%}{%endif%}{%ifhttp_sample.request.raw_data%} false {{http_sample.request.raw_data}} = {%endif%} {{http_sample.request.domain}} {{http_sample.request.port}} {{http_sample.request.protocol}} {{http_sample.request.encoding}} {{http_sample.request.path}} {{http_sample.request.method}} {{http_sample.request.follow_redirects}} {{http_sample.request.auto_redirects}} {{http_sample.request.use_keepalive}} false {{http_sample.request.connect_timeout}} {{http_sample.request.response_timeout}} {%ifhttp_sample.request.headers%} {%endfor%}{%endif%}{%ifthread_group.dubbo_samples%}{%fordubbo_sampleinthread_group.dubbo_samples%}{%forname,valueinhttp_sample.request.headers.items()%} {{name}} {{value}} {%endfor%} {%endif%}{%ifhttp_sample.csv_files%}{%forcsv_fileinhttp_sample.csv_files%} {{csv_file.delimiter}} UTF_8 dat/{{csv_file.path}} true false true shareMode.group false {{csv_file.varnames}} {%endfor%}{%endif%}{%ifhttp_sample.validate%}{%forassertioninhttp_sample.validate%} {%ifassertion.strings%}{%forstringinassertion.strings%} {{string}}{%endfor%}{%endif%} Assertion.{{assertion.test_field}} false {{assertion.test_type}} {%endfor%}{%endif%} false saveConfig true true true true
true true true false true true false false false true false false false true 0 true true true true true true {{dubbo_sample.registry.type}} {{dubbo_sample.registry.group}} dubbo:// {{dubbo_sample.registry.address}} {{dubbo_sample.dubbo.timeout}} {{dubbo_sample.dubbo.retries}} {{dubbo_sample.dubbo.group}} {{dubbo_sample.dubbo.connections}} {{dubbo_sample.dubbo.load_balance}} sync {{dubbo_sample.dubbo.cluster}} {{dubbo_sample.dubbo.service}} {{dubbo_sample.dubbo.method}} 1{%forparamindubbo_sample.dubbo.params%} {{param.type}} {{param.value}}{%endfor%} 0 {%ifdubbo_sample.dubbo.headers%} {%forname,valueindubbo_sample.dubbo.headers.items()%} {{name}} {{value}} {%endfor%} {%endif%}{%ifdubbo_sample.validate%}{%forassertionindubbo_sample.validate%} {%ifassertion.strings%}{%forstringinassertion.strings%} {{string}}{%endfor%}{%endif%} Assertion.{{assertion.test_field}} false {{assertion.test_type}} {%endfor%}{%endif%} {%endfor%}{%endif%}{%endfor%}
组装出类似data.yaml格式的数据,并使用jinja2渲染模板即可得到完整的jmx文件
pipinstallpyyamljinja2
importyaml importjinja2 #组装或读取数据 withopen('data.yaml',encoding='utf-8')asf: data=yaml.safe_load(f) #读取模板 withopen('tpl.xml',encoding='utf-8')asf: tpl=f.read() #渲染模板生成jmx jmx=jinja2.Template(tpl).render(data) withopen(jmx_file,'w',encoding='utf-8')asf: f.write(jmx)
后计
在实际项目中,还涉及数据文件的拷贝,节点环境的部署,脚本的分发,报告的下载等等,可以使用paramiko或者fabric或ansible完成,压测节点数据分发的服务管理。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。