Python生成JMeter测试脚本

生成初始化脚本

初始化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")

其他组件实现方式待续(后续将实现断言、信息头管理器、查看结果树等组件)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值