生成初始化脚本
初始化JMeter脚本的最外层标签,后期会加上运行测试,所以下面的信息配置成本地的JMeter信息
def get():
jmeter_test_plan = ET.Element('jmeterTestPlan')
jmeter_test_plan.set('version', '1.2')
jmeter_test_plan.set('properties', '5.0')
jmeter_test_plan.set('jmeter', '5.6.3')
return jmeter_test_plan
生成测试计划组件
测试计划组件,使用set函数,添加测试计划需要的参数后,用get方法生成。因为用户自定义变量实际在脚本中是保存到测试计划标签中,所以实体中有用户设置定义的变量参数。注意,测试计划外层有个hashtree标签,需要手写一个hashtree标签,然后将测试计划添加到hashtree标签中,再将hashtree标签添加到最外层的JMeter标签中,具体可参考文章最后的demo。
import xml.etree.ElementTree as ET
from base import General
class JMeterTestPlan:
def __init__(self):
self.__test_plan_name = '测试计划'
# 函数测试模式
self.__functional_mode = 'false'
# 独立运行没给线程组
self.__serialize_threadgroups = 'false'
# 主线程结束后运行tearDown
self.__tearDown_on_shutdown = 'false'
# 用户设置定义的变量
self.__user_defined_variables = {}
def get_test_plan_name(self):
return self.__test_plan_name
def set_test_plan_name(self, test_plan_name):
self.__test_plan_name = test_plan_name
def get_functional_mode(self):
return self.__functional_mode
def set_funtional_mode(self, funtional_mode):
self.__functional_mode = funtional_mode
def get_serialize_threadgroups(self):
return self.__serialize_threadgroups
def set_serialize_threadgroups(self, serialize_threadgroups):
self.__serialize_threadgroups = serialize_threadgroups
def get_teardown_on_shutdown(self):
return self.__tearDown_on_shutdown
def set_teardown_on_shutdown(self, teardown_on_shutdown):
self.__tearDown_on_shutdown = teardown_on_shutdown
def get_user_defined_variables(self):
return self.__user_defined_variables
def set_user_defined_variables(self, user_defined_variables):
self.__user_defined_variables = user_defined_variables
def get(self, parent):
test_plan = ET.SubElement(parent, 'TestPlan')
test_plan.set('guiclass', 'TestPlanGui')
test_plan.set('testclass', 'TestPlan')
test_plan.set('testname', self.__test_plan_name)
elemet_prop = ET.SubElement(test_plan, 'elementProp')
elemet_prop.set('name', 'TestPlan.user_defined_variables')
elemet_prop.set('elementType', 'Arguments')
elemet_prop.set('guiclass', 'ArgumentsPanel')
elemet_prop.set('testclass', 'Arguments')
elemet_prop.set('testname', '用户定义的变量')
collection_prop = ET.SubElement(elemet_prop, 'collectionProp')
collection_prop.set('name', 'Arguments.arguments')
if self.__user_defined_variables:
for key, value in self.__user_defined_variables.items():
variables_elem = ET.SubElement(collection_prop, 'elementProp')
variables_elem.set('name', key)
variables_elem.set('elementType', 'Argument')
str_prop1 = ET.SubElement(variables_elem, 'stringProp')
str_prop1.set('name', 'Argument.name')
str_prop1.text = str(key)
str_prop2 = ET.SubElement(variables_elem, 'stringProp')
str_prop2.set('name', 'Argument.value')
str_prop2.text = str(value)
str_prop3 = ET.SubElement(variables_elem, 'stringProp')
str_prop3.set('name', 'Argument.metadata')
str_prop3.text = '='
functional_mode_prop = ET.SubElement(test_plan, 'boolProp')
functional_mode_prop.set('name', 'TestPlan.functional_mode')
functional_mode_prop.text = self.__functional_mode
serialize_threadgroups_prop = ET.SubElement(test_plan, 'boolProp')
serialize_threadgroups_prop.set('name', 'TestPlan.serialize_threadgroups')
serialize_threadgroups_prop.text = self.__serialize_threadgroups
tearDown_on_shutdown_prop = ET.SubElement(test_plan, 'boolProp')
tearDown_on_shutdown_prop.set('name', 'TestPlan.tearDown_on_shutdown')
tearDown_on_shutdown_prop.text = self.__tearDown_on_shutdown
return test_plan
生成线程组
线程组组件,通用使用set方法更改组件中的参数,设置好参数后使用get方法生成,然后添加到对应的测试计划标签下级中,线程组外层也有个hashtree标签,需要手写后,将线程组添加到hashtree中。
import xml.etree.ElementTree as ET
from base import General
class JMeterThreadGroup:
def __init__(self):
self.__test_plan_name = '测试计划'
self.__thread_group_name = '线程组'
# 线程数
self.__num_threads = 1
# 注释
self.__thread_comments = ''
# 间隔时间
self.__ramp_time = 1
self.__same_user_on_next_iteration = 'true'
# 取样器错误后执行动作 continue 继续 startnextloop 启动下一个进程循环 stopthread 停止线程 stoptest 停止测试 stoptestnow 立即停止测试
self.__on_sample_error = 'continue'
# 循环次数
self.__loops = 1
# 调度器持续时间 /秒
self.__duration = ''
# 调度器启动延迟 /秒
self.__delay = ''
# 延迟创建线程知道需要
self.__delayedStart = 'false'
# 调度器
self.__scheduler = 'false'
def get_test_plan_name(self):
return self.__test_plan_name
def set_test_paln_name(self, test_plan_name):
self.__test_plan_name = test_plan_name
def get_thread_group_name(self):
return self.__thread_group_name
def set_thread_group_name(self, thread_group_name):
self.__thread_group_name = thread_group_name
def get_num_threads(self):
return self.__num_threads
def set_num_threads(self, num_threads):
self.__num_threads = num_threads
def get_thread_comments(self):
return self.__thread_comments
def set_thread_comments(self, thread_comments):
self.__thread_comments = thread_comments
def get_ramp_time(self):
return self.__ramp_time
def set_ramp_time(self, ramp_time):
self.__ramp_time = ramp_time
def get_same_user_on_next_iteration(self):
return self.__same_user_on_next_iteration
def set_same_user_on_next_iteration(self, same_user_on_next_iteration):
self.__same_user_on_next_iteration = same_user_on_next_iteration
def get_on_sample_error(self):
return self.__on_sample_error
def set_on_sample_error(self, on_sample_error):
self.__on_sample_error = on_sample_error
def get_loops(self):
return self.__loops
def set_loops(self, loops):
self.__loops = loops
def get_duration(self):
return self.__duration
def set_duration(self, duration):
self.__duration = duration
def get_delay(self):
return self.__delay
def set_delay(self, delay):
self.__delay = delay
def get_delayedStart(self):
return self.__delayedStart
def set_delayedStart(self, delayedStart):
self.__delayedStart = delayedStart
def get_scheduler(self):
return self.__scheduler
def set_scheduler(self, scheduler):
self.__scheduler = scheduler
def items(self):
dicts = {
'comments': self.__thread_comments,
'num_threads':self.__num_threads,
'ramp_time': self.__ramp_time,
'same_user_on_next_iteration': self.__same_user_on_next_iteration,
'on_sample_error': self.__on_sample_error,
'duration': self.__duration,
'delay': self.__delay,
'delayedStart': self.__delayedStart,
'scheduler': self.__scheduler,
'loops': self.__loops
}
return dicts
def add_loop_controller(self, parent, loops):
element_prop = ET.SubElement(parent, 'elementProp')
element_prop.set('name', 'ThreadGroup.main_controller')
element_prop.set('elementType', 'LoopController')
element_prop.set('guiclass', 'LoopControlPanel')
element_prop.set('testclass', 'LoopController')
element_prop.set('testname', '循环控制器')
General.add_int_prop(self, element_prop, 'LoopController.loops', loops)
General.add_bool_prop(self, parent, '.LoopController.continue_forever', 'false')
def get(self, parent):
thread_group = ET.SubElement(parent,'ThreadGroup')
thread_group.set('guiclass', 'ThreadGroupGui')
thread_group.set('testclass', 'ThreadGroup')
thread_group.set('testname', self.__thread_group_name)
prop_map = {
'comments': General.add_str_prop,
'num_threads': General.add_int_prop,
'ramp_time': General.add_int_prop,
'same_user_on_next_iteration': General.add_bool_prop,
'on_sample_error': General.add_str_prop,
'duration': General.add_long_prop,
'delay': General.add_long_prop,
'delayedStart': General.add_bool_prop,
'scheduler': General.add_bool_prop
}
keys_map = {
'comments': 'TestPlan.comments',
'num_threads': 'ThreadGroup.num_threads',
'ramp_time': 'ThreadGroup.ramp_time',
'same_user_on_next_iteration': 'ThreadGroup.same_user_on_next_iteration',
'on_sample_error': 'ThreadGroup.on_sample_error',
'duration': 'ThreadGroup.duration',
'delay': 'ThreadGroup.delay',
'delayedStart': 'ThreadGroup.delayedStart',
'scheduler': 'ThreadGroup.scheduler'
}
for key, value in self.items().items():
if key == 'loops':
self.add_loop_controller(thread_group, value)
elif key in prop_map:
if value == '' or value == 'false':
pass
else:
prop_map[key](self, thread_group, keys_map[key], value)
return thread_group
生成HTTP监听器
HTTP监听器组件,通用是用set方法修改参数后,使用get方法生成,将生成的标签添加到线程组的hashtree中。同时需要注意的是,__postBodyRaw和__params只能使用一个方式,__files可以和他们两个任意一个搭配使用。
import mimetypes
import xml.etree.ElementTree as ET
from base import General
class JMeterHttpSampler:
def __init__(self):
# 名称
self.__http_sampler_name = 'http请求'
# 注释
self.__comments = ''
# 服务器名称或IP
self.__domain = ''
# 端口
self.__port = ''
# 协议
self.__protocol = ''
# 内容编码
self.__contentEncoding = ''
# 路径
self.__path = ''
# 请求方法
self.__method = 'GET'
# 跟随重定向
self.__follow_redirects = 'true'
# 使用KeepAlive
self.__use_keepalive = 'true'
# 自动重定向
self.__auto_redirects = 'false'
# 对POST使用multipart/form-data
self.__DO_MULTIPART_POST = 'false'
# 与浏览器兼容的头
self.__BROWSER_COMPATIBLE_MULTIPART = 'false'
# 消息体数据
self.__postBodyRaw = {}
# 参数
self.__params = {}
# 文件
self.__files = {}
def get_http_sampler_name(self):
return self.__http_sampler_name
def set_http_sampler_name(self, http_sampler_name):
self.__http_sampler_name = http_sampler_name
def get_comments(self):
return self.__comments
def set_comments(self, comments):
self.__comments = comments
def get_domain(self):
return self.__domain
def set_domain(self, domain):
self.__domain = domain
def get_port(self):
return self.__port
def set_port(self, port):
self.__port = port
def get_protocol(self):
return self.__protocol
def set_protocol(self, protocol):
self.__protocol = protocol
def get_contentEncoding(self):
return self.__contentEncoding
def set_contentEncoding(self, contentEncoding):
self.__contentEncoding = contentEncoding
def get_path(self):
return self.__path
def set_path(self, path):
self.__path = path
def get_method(self):
return self.__method
def set_method(self, method):
self.__method = method
def get_follow_redirects(self):
return self.__follow_redirects
def set_follow_redirects(self, follow_redirects):
self.__follow_redirects = follow_redirects
def get_use_keepalive(self):
return self.__use_keepalive
def set_use_keepalive(self, use_keepalive):
self.__use_keepalive = use_keepalive
def get_auto_redirects(self):
return self.__auto_redirects
def set_auto_redirects(self, auto_redirects):
self.__auto_redirects = auto_redirects
def get_DO_MULTIPART_POST(self):
return self.__DO_MULTIPART_POST
def set_DO_MULTIPART_POST(self, DO_MULTIPART_POST):
self.__DO_MULTIPART_POST = DO_MULTIPART_POST
def get_BROWSER_COMPATIBLE_MULTIPART(self):
return self.__BROWSER_COMPATIBLE_MULTIPART
def set_BROWSER_COMPATIBLE_MULTIPART(self, BROWSER_COMPATIBLE_MULTIPART):
self.__BROWSER_COMPATIBLE_MULTIPART = BROWSER_COMPATIBLE_MULTIPART
def get_postBodyRaw(self):
return self.__postBodyRaw
def set_postBodyRaw(self, postBodyRaw):
self.__postBodyRaw = postBodyRaw
def get_params(self):
return self.__params
def set_params(self, params):
self.__params = params
def get_files(self):
return self.__files
def set_files(self, files):
self.__files = files
def items(self) -> dict:
dicts = {
'comments': self.__comments,
'domain': self.__domain,
'port': self.__port,
'protocol': self.__protocol,
'contentEncoding': self.__contentEncoding,
'path': self.__path,
'follow_redirects': self.__follow_redirects,
'method': self.__method,
'use_keepalive': self.__use_keepalive,
'auto_redirects': self.__auto_redirects,
'DO_MULTIPART_POST': self.__DO_MULTIPART_POST,
'BROWSER_COMPATIBLE_MULTIPART': self.__BROWSER_COMPATIBLE_MULTIPART,
'postBodyRaw': self.__postBodyRaw,
'params': self.__params,
'files': self.__files
}
return dicts
def add_arguments(self, parent, value):
bool_prop = ET.SubElement(parent, 'boolProp')
bool_prop.set('name', 'HTTPSampler.postBodyRaw')
bool_prop.text = 'true'
element_prop = ET.SubElement(parent, 'elementProp')
element_prop.set('name', 'HTTPsampler.Arguments')
element_prop.set('elementType', 'Arguments')
collection_prop = ET.SubElement(element_prop, 'collectionProp')
collection_prop.set('name', 'Arguments.arguments')
body_prop = ET.SubElement(collection_prop, 'elementProp')
body_prop.set('name', '')
body_prop.set('elementType', 'HTTPArgument')
encode_prop = ET.SubElement(body_prop, 'boolProp')
encode_prop.set('name', 'HTTPArgument.always_encode')
encode_prop.text = 'false'
bodys = ET.SubElement(body_prop, 'stringProp')
bodys.set('name', 'Argument.value')
bodys.text = str(value).replace('\'', '\"')
metadata_prop = ET.SubElement(body_prop, 'stringProp')
metadata_prop.set('name', 'Argument.metadata')
metadata_prop.text = '='
def add_params(self, parent, values):
bool_prop = ET.SubElement(parent, 'boolProp')
bool_prop.set('name', 'HTTPSampler.postBodyRaw')
bool_prop.text = 'false'
element_prop = ET.SubElement(parent, 'elementProp')
element_prop.set('name', 'HTTPsampler.Arguments')
element_prop.set('elementType', 'Arguments')
collection_prop = ET.SubElement(element_prop, 'collectionProp')
collection_prop.set('name', 'Arguments.arguments')
for key, value in values.items():
body_prop = ET.SubElement(collection_prop, 'elementProp')
body_prop.set('name', key)
body_prop.set('elementType', 'HTTPArgument')
General.add_bool_prop(self, body_prop, 'HTTPArgument.always_encode', 'false')
General.add_str_prop(self, body_prop, 'Argument.value', str(value))
General.add_str_prop(self, body_prop, 'Argument.metadata', '=')
General.add_bool_prop(self, body_prop, 'HTTPArgument.use_equals', 'true')
General.add_str_prop(self, body_prop, 'Argument.name', key)
def add_files(self, parent, values):
bool_prop = ET.SubElement(parent, 'boolProp')
bool_prop.set('name', 'HTTPSampler.postBodyRaw')
if self.__postBodyRaw:
bool_prop.text = 'true'
else:
bool_prop.text = 'false'
element_prop = ET.SubElement(parent, 'elementProp')
element_prop.set('name', 'HTTPsampler.Files')
element_prop.set('elementType', 'HTTPFileArgs')
collection_prop = ET.SubElement(element_prop, 'collectionProp')
collection_prop.set('name', 'HTTPFileArgs.files')
for key, value in values.items():
body_prop = ET.SubElement(collection_prop, 'elementProp')
body_prop.set('name', str(value))
body_prop.set('elementType', 'HTTPFileArg')
General.add_str_prop(self, body_prop, 'File.path', str(value))
General.add_str_prop(self, body_prop, 'File.paramname', key)
mime_type, encoding = mimetypes.guess_type(str(value))
General.add_str_prop(self, body_prop, 'File.mimetype', mime_type)
def get(self, parent):
hash_tree = ET.SubElement(parent, 'hashTree')
http_sampler = ET.SubElement(hash_tree, 'HTTPSamplerProxy')
http_sampler.set('guiclass', 'HttpTestSampleGui')
http_sampler.set('testclass', 'HTTPSamplerProxy')
http_sampler.set('testname', self.__http_sampler_name)
prop_map = {
'comments': General.add_str_prop,
'domain': General.add_str_prop,
'port': General.add_str_prop,
'protocol': General.add_str_prop,
'contentEncoding': General.add_str_prop,
'path': General.add_str_prop,
'follow_redirects': General.add_bool_prop,
'method': General.add_str_prop,
'use_keepalive': General.add_bool_prop,
'auto_redirects': General.add_bool_prop,
'DO_MULTIPART_POST': General.add_bool_prop,
'BROWSER_COMPATIBLE_MULTIPART': General.add_bool_prop,
}
keys_map = {
'comments': 'TestPlan.comments',
'domain': 'HTTPSampler.domain',
'port': 'HTTPSampler.port',
'protocol': 'HTTPSampler.protocol',
'contentEncoding': 'HTTPSampler.contentEncoding',
'path': 'HTTPSampler.path',
'follow_redirects': 'HTTPSampler.follow_redirects',
'method': 'HTTPSampler.method',
'use_keepalive': 'HTTPSampler.use_keepalive',
'auto_redirects': 'HTTPSampler.auto_redirects',
'DO_MULTIPART_POST': 'HTTPSampler.DO_MULTIPART_POST',
'BROWSER_COMPATIBLE_MULTIPART': 'HTTPSampler.BROWSER_COMPATIBLE_MULTIPART',
}
for key, value in self.items().items():
if self.__postBodyRaw and key == 'postBodyRaw':
self.add_arguments(http_sampler, value)
elif self.__params and key == 'params':
self.add_params(http_sampler, value)
elif self.__files and key == 'files':
self.add_files(http_sampler, value)
elif key in prop_map:
if value == 'false' or value == '':
pass
else:
prop_map[key](self, http_sampler, keys_map[key], value)
return hash_tree
生成脚本demo
根据JMeter的GUI工具,代码中与GUI工具保持一致,填写好默认值。
import xml.etree.ElementTree as ET
import JMeter
from JMeterThreadGroup import JMeterThreadGroup
from JMeterTestPlan import JMeterTestPlan
from JMeterHttpSampler import JMeterHttpSampler
import base
class GenJMeter:
jmeter_script = JMeter.get()
hash_tree = ET.SubElement(jmeter_script, 'hashTree')
test_plan = JMeterTestPlan()
test_plan = test_plan.get(hash_tree)
thread_group_tree = ET.SubElement(hash_tree, 'hashTree')
thread_group = JMeterThreadGroup().get(thread_group_tree)
http_sampler = JMeterHttpSampler()
http_sampler.set_params({'test': 123})
http_sampler.set_files({'file': 'test.txt'})
http_sampler.get(thread_group_tree)
bases = base.General()
tree = ET.ElementTree(jmeter_script)
bases.indent(jmeter_script)
with open('test.jmx', "wb") as f:
tree.write(f, encoding="utf-8", xml_declaration=True, method="xml")
其他组件实现方式待续(后续将实现断言、信息头管理器、查看结果树等组件)