使用Beautiful Soup等三种方式定制Jmeter测试脚本

news2024/11/25 20:18:21

目录

背景介绍

实现思路

把脚本数据读出,使用正则表达式(re库)匹配关键数据进行修改

把脚本数据读出,使用BeautifulSoup的xml解析功能解析后修改

通过Beautiful Soup

Beautiful Soup

具体实现

使用string.Template字符替换

具体实现

使用re.sub

延展

 资料获取方法


背景介绍

我们在做性能调优时,时常需要根据实际压测的情况,调整线程组的参数,比如循环次数,线程数,所有线程启动的时间等。
如果是在一台Linux机器上,就免不了在本机打开图形页面修改,然后最后传递到压测机上面的过程,所有为了解决这个业务痛点
,使用Python写了一个能直接修改Jmeter基础压测参数的脚本,能修改jmx脚本的线程组数、循环次数、线程组全部启动需要花的时间。

实现思路

刚开始准备写这个脚本的时候,想了两个思路:

把脚本数据读出,使用正则表达式(re库)匹配关键数据进行修改

优点:可以快速的改写数据
缺点:无法进行区块的修改

把脚本数据读出,使用BeautifulSoup的xml解析功能解析后修改

注:我们的Jmx脚本其实就是一个标准格式的xml

优点: 能快速的查找元素并进行修改
缺点: 需要熟悉BeautifulSoup的用法

通过Beautiful Soup

Beautiful Soup

Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.我们使用BeautifulSoup解析xml或者html的时候,能够得到一个 BeautifulSoup 的对象,我们可以通过操作这个对象来完成原始数据的结构化数据。具体的使用可以参照这份文档。

具体实现

主要使用了bs4的soup.find 和self.soup.find_all功能。结化或数据的修改如loops.string = num

值得注意的是,find_all支持正则匹配,甚至如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数。

修改后的脚本将以"T{}L{}R{}-{}_{}.jmx".format(thread_num, loop_num, ramp_time, self.src_script, self.get_time()) 的形式保存,具体封装如下:

import time
import os
from bs4 import BeautifulSoup

class OpJmx:
    def __init__(self, file_name):
        self.src_script = self._split_filename(file_name)
        with open(file_name, "r") as f:
            data = f.read()
        self.soup = BeautifulSoup(data, "xml")

    @staticmethod
    def _split_filename(filename):
        """
        新生成的文件兼容传入相对路径及文件名称
        :param filename:
        :return:
        """
        relative = filename.split("/")
        return relative[len(relative)-1].split(".jmx")[0]

    def _theard_num(self):
        """
        :return: 线程数据对象
        """
        return self.soup.find("stringProp", {"name": {"ThreadGroup.num_threads"}})

    def _ramp_time(self):
        """
        :return: 启动所有线程时间配置对象
        """
        return self.soup.find("stringProp", {"name": {"ThreadGroup.ramp_time"}})

    def _bean_shell(self):
        """
        :return:  bean_shell对象
        """
        return self.soup.find("stringProp", {"name": {"BeanShellSampler.query"}})

    def _paths(self):
        """
        :return: 请求路径信息对象
        """
        return self.soup.find_all("stringProp", {"name": {"HTTPSampler.path"}})

    def _methods(self):
        """
        :return: 请求方法对象
        """
        return self.soup.find_all("stringProp", {"name": {"HTTPSampler.method"}})

    def _argument(self):
        """
        :return: post请求参数对象
        """
        # Argument.value 不唯一 通过HTTPArgument.always_encode找到
        return self.soup.find_all("boolProp", {"name": {"HTTPArgument.always_encode"}})[0].find_next()

    def _loops(self):
        """
        循环次数,兼容forever 与具体次数
        :return: 循环次数对象
        """
        _loops = self.soup.find("stringProp", {"name": {"LoopController.loops"}})
        if _loops:
            pass
        else:
            _loops = self.soup.find("intProp", {"name": {"LoopController.loops"}})

        return _loops

    @staticmethod
    def get_time():
        return time.strftime("%Y-%m-%d@%X", time.localtime())

    def get_bean_shell(self):
        _str = self._bean_shell().string
        logger.info("bean_shell: " + _str)
        return _str

    def set_bean_shell(self, new_bean_shell):
        old_bean_shell = self._bean_shell()
        old_bean_shell.string = new_bean_shell

    def get_ramp_time(self):
        _str = self._ramp_time().string
        logger.info("ramp_time: " + _str)
        return _str

    @check_num
    def set_ramp_time(self, num):
        loops = self._ramp_time()
        loops.string = num

    def get_loops(self):
        _str = self._loops().string
        logger.info("loops: " + _str)
        return _str

    @check_num
    def set_loops(self, num):
        """
        :param num: -1 为一直循环,其他为具体循环次数
        :return:
        """
        loops = self._loops()
        loops.string = num

    def get_argument(self):
        _str = self._argument().string
        logger.info("argument: " + _str)
        return _str

    def set_argument(self, **kwargs):
        """
        设置请求参数(JSON,传入字典)
        :param kwargs:
        :return:
        """
        param = self._argument()
        param.string = str(kwargs)

    def get_thread_num(self):
        _str = self._theard_num().string
        logger.info("thread_num: " + _str)
        return _str

    @check_num
    def set_thread_num(self, num):
        """
        设置线程数信息
        :param num:
        :return:
        """
        thread_num = self._theard_num()
        thread_num.string = num
        # print(self.soup.find_all("stringProp", {"name": {"ThreadGroup.num_threads"}})[0].string)

    def mod_header(self, key, value, index=0):
        """
        修改指定header的信息,默认修改第一个值
        :param key:
        :param value:
        :param index:
        :return:
        """
        headers = self.soup.find_all("elementProp", {"elementType": {"Header"}})
        headers[index].find("stringProp", {"name": {"Header.name"}}).string = key
        headers[index].find("stringProp", {"name": {"Header.value"}}).string = value
        # for header in headers:
        #     header.find("stringProp", {"name": {"Header.name"}}).string = key
        #     header.find("stringProp", {"name": {"Header.value"}}).string = value

    def save_jmx(self):
        logger.info("参数设置完毕,开始保存数据")
        cur_path = os.path.dirname(os.path.realpath(__file__))
        thread_num = self.get_thread_num()
        loop_num = self.get_loops()
        ramp_time = self.get_ramp_time()

        script_name = "T{}L{}R{}-{}_{}.jmx".format(thread_num, loop_num, ramp_time, self.src_script, self.get_time())
        script_path = os.path.join(cur_path, '..', 'script')

        if not os.path.exists(script_path):
            os.mkdir(script_path)

        script_location = os.path.join(script_path, script_name)
        logger.info("测试脚本已保存于 {}".format(script_location))
        with open(script_location, "w") as f:
            f.write(str(self.soup))

        return script_name
if __name__ == '__main__':
    jmx = OpJmx("templates/template.jmx")
    argvs = sys.argv
    len_argvs = len(argvs) - 1
    if len_argvs == 0:
        pass
    elif len_argvs == 1:
        jmx.set_thread_num(argvs[1])
    elif len_argvs == 2:
        jmx.set_thread_num(argvs[1])
        jmx.set_loops(argvs[2])
    elif len_argvs == 3:
        jmx.set_thread_num(argvs[1])
        jmx.set_loops(argvs[2])
        jmx.set_ramp_time(argvs[3])
    jmx.save_jmx()

未完待续...

使用string.Template字符替换

如果只是简单的字符串替换,使用 format 或者 %s 也能完成,选择使用string.Template的原因是string.Template可以自动化匹配规则,且能修改操作符,
而不管是fstring还是format都是用的{}来进行关键字的定位,{}在jmx脚本中本身就存在特定的意义。

思路:

  • 修改jmx脚本中的关键数据,使用特定操作符
  • 定义相关字典,使用safe_substitute进行赋值

具体实现

#! /usr/bin/python
# coding:utf-8 
""" 
@author:Bingo.he 
@file: str_temp.py 
@time: 2019/08/20 
"""
import string

# with open("template_str.jmx", "r") as f:
#     data = f.read()
set_value = {
    "num_threads": 10,
    "loops": 1011,
    "ramp_time": 10
}
str_temp = """
  <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
    <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
    <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
      <boolProp name="LoopController.continue_forever">false</boolProp>
      <stringProp name="LoopController.loops">%loops</stringProp>
    </elementProp>
    <stringProp name="ThreadGroup.num_threads">%num_threads</stringProp>
    <stringProp name="ThreadGroup.ramp_time">%ramp_time</stringProp>
    <boolProp name="ThreadGroup.scheduler">false</boolProp>
    <stringProp name="ThreadGroup.duration"></stringProp>
    <stringProp name="ThreadGroup.delay"></stringProp>
  </ThreadGroup>
"""


class MyTemplate(string.Template):
    # 修改操作符为"%"
    delimiter = '%'
    # 修改匹配规则(正则)
    # idpattern = '[a-z]+_[a-z]+'


t = MyTemplate(str_temp)

print(t.safe_substitute(set_value))

输出:

...
  <stringProp name="LoopController.loops">1011</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">101</stringProp>
<stringProp name="ThreadGroup.ramp_time">10</stringProp>
...

使用re.sub

str_temp = """
  <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
    <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
    <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
      <boolProp name="LoopController.continue_forever">false</boolProp>
      <stringProp name="LoopController.loops">$loops</stringProp>
    </elementProp>
    <stringProp name="ThreadGroup.num_threads">$num_threads</stringProp>
    <stringProp name="ThreadGroup.ramp_time">$ramp_time</stringProp>
    <boolProp name="ThreadGroup.scheduler">false</boolProp>
    <stringProp name="ThreadGroup.duration"></stringProp>
    <stringProp name="ThreadGroup.delay"></stringProp>
  </ThreadGroup>
"""

str_l = re.sub(r"\$loops", "101", str_temp)
str_t = re.sub(r"\$num_threads", "102", str_l)
str_r = re.sub(r"\$ramp_time", "103", str_t)

print(str_r)

输出:

···
      <boolProp name="LoopController.continue_forever">false</boolProp>
      <stringProp name="LoopController.loops">101</stringProp>
    </elementProp>
    <stringProp name="ThreadGroup.num_threads">102</stringProp>
    <stringProp name="ThreadGroup.ramp_time">103</stringProp>
···

延展

相信大家也注意到了,我们每替换一个参数都需要调用一次re.sub,而且要将上一次调用的输出作为下一次的输入,像极了递归调用。但是我们今天不介绍递归改写的方法,而是使用闭包的方式,具体的例子如下:

import re


def multiple_replace(text, adict):
    rx = re.compile('|'.join(map(re.escape, adict)))

    def one_xlat(match):
        return adict[match.group(0)]

    return rx.sub(one_xlat, text)  # 每遇到一次匹配就会调用回调函数


# 把key做成了 |分割的内容,也就是正则表达式的OR
map1 = {'1': '2', '3': '4', '5': '6'}
_str = '113355'
print(multiple_replace(_str, map1))

文中可能存在描述不正确,欢迎大神们指正补充!


 资料获取方法

【留言777】

各位想获取源码等教程资料的朋友请点赞 + 评论 + 收藏,三连!

三连之后我会在评论区挨个私信发给你们~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/849468.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jupyter Notebook 遇上 NebulaGraph,可视化探索图数据库

在之前的《手把手教你用 NebulaGraph AI 全家桶跑图算法》中&#xff0c;除了介绍了 ngai 这个小工具之外&#xff0c;还提到了一件事有了 Jupyter Notebook 插件: https://github.com/wey-gu/ipython-ngql&#xff0c;可以更便捷地操作 NebulaGraph。 本文就手把手教你咋在 J…

BpBinder与PPBinder调用过程——Android开发Binder IPC通信技术

在Android系统中&#xff0c;进程间通信&#xff08;IPC&#xff09;是一个非常重要的话题。Android系统通过Binder IPC机制实现进程间通信&#xff0c;而Binder IPC通信技术则是Android系统中最为重要的进程间通信技术之一。本文将介绍Binder IPC通信技术的原理&#xff0c;并…

实测有效Window10系统解决文件名过长无法删除或移动问题

问题&#xff1a;window10家庭版&#xff0c;文件名字太长无法对其进行操作 如图 PS&#xff1a;什么注册表方法&#xff0c;压缩方法都没效果 解决&#xff1a; 打开 注册编辑器 进入路径 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem 修改 LongP…

Jenkins 修改默认管理员帐号

1、新增一个新的超级管理员用户&#xff0c;并验证能正常登录 2、进入 Jenkins 用户管理目录&#xff1a; /data/software/jenkins/users 3、修改超级管理文件夹的名称为其他名称&#xff0c;如&#xff1a;mv admin_*** ifadm_*** 4、重启Jenkins容器

snowboy+sherpa-onnx+Rasa+Coqui实现语音音箱【语音助手】

背景 本系列主要目标初步完成一款智能音箱的基础功能&#xff0c;包括语音唤醒、语音识别(语音转文字)、处理用户请求&#xff08;比如查天气等&#xff0c;主要通过rasa自己定义意图实现&#xff09;、语音合成(文字转语音)功能。 coqui主要在项目中完成接收rasa响应的内容&…

Postgresql源码(111)dms框架进程信号发送与处理流程

信号处理整体流程 信号从bgworker发出后&#xff0c;主进程将ParallelMessagePending置为true&#xff0c;下次CHECK_FOR_INTERRUPTS()时&#xff0c;会进入信号处理逻辑中&#xff1a;HandleParallelMessages。进入信号处理逻辑后&#xff0c;首先遍历所有现存的ParallelCont…

护网专题简单介绍

护网专题简单介绍 一、护网红蓝队介绍1.1、网络安全大事件1.2、护网行动由来1.3、护网行动中的角色二、红队介绍2.1、红队所需技能2.2、红队攻击流程 三、蓝队介绍3.1、蓝队所需技能3.2、蓝队防守四阶段3.3、蓝队前期准备 四、常见安全厂商介绍4.1、常见安全厂商 五、常见安全产…

Softing工业获得自动化产品安全开发流程认证

Softing工业获得了TV Sd颁发的IEC 62443-4-1产品安全开发流程认证。 &#xff08;IEC 62443-4-1认证确保网络安全&#xff09; 截至2023年6月&#xff0c;位于德国哈尔和纽伦堡的工厂以及罗马尼亚克卢日的Softing工业研发部门已获得IEC 62443-4-1:2018标准的认证。该认证流程由…

Ajax-概念、Http协议、Ajax请求及其常见问题

Ajax Ajax概念Ajax优缺点HTTP协议请求报文响应报文 Ajax案例准备工作express基本使用创建一个服务器 发送AJAX请求GET请求POST请求JSON响应 Ajax请求出现的问题IE缓存问题Ajax请求超时与网络异常处理Ajax手动取消请求Ajax重复发送请求问题 Ajax概念 AJAX 全称为Asynchronous J…

《孙子兵法》快速概览,有哪些章节?趣讲《孙子兵法》【第2讲】

《孙子兵法》快速概览&#xff0c;有哪些章节&#xff1f;趣讲《孙子兵法》【第2讲】 《孙子兵法》十一家注是一个有名的版本&#xff0c;十一家注是曹操、杜牧等十一人注释&#xff0c;曹操是真正的军事家&#xff0c;是名副其实的大咖。总共三卷十三篇&#xff0c;比较难记住…

Unity 3D中使用tilemap创建关卡地图,瓦片间隙有漏缝

我们使用一张图片来作为Sprite图集&#xff0c;创建地形图&#xff1a; 运行后&#xff0c;会发现&#xff0c;瓦片之间似乎总是有间距。 检查了图片发现&#xff0c;并不是图片边界存在间隙。 最后发现问题是出在图片资源中的线性过滤属性值&#xff1a; 在设计界面就能够看…

【三维编辑】SPIn-NeRF:多视图分割与感知修复(CVPR 2023)

文章目录 摘要一、简介二、相关工作1.Image Inpainting2.NeRF 操作3. 背景: NeRF 知识 三、方法3.1.多视图分割3.1.1掩码初始化3.1.2基于nerf的分割 3.2.多视图 Inpainting3.2.1 RGB先验3.2.2深度先验3.2.3基于patch 的优化3.2.4掩码精炼 四、实验五、安装与代码讲解1.项目安装…

一篇文章看懂Apipost Mock功能怎么用

在接口开发过程中&#xff0c;Mock功能可以帮助开发者快速测试和验证接口的正确性和稳定性&#xff0c;以便快速迭代和修复问题。Apipost推出智能Mock功能&#xff0c;可以在智能期望中填写一些触发条件&#xff0c;开启后&#xff0c;Apipost会根据已设置的触发条件&#xff0…

Linux-GPIO 配置pull up、pull down、no pull

author daisy.skye的博客_CSDN博客-Qt,嵌入式,Linux领域博主 https://blog.csdn.net/qq_40715266?typeblog 系列基于RK3568的Linux驱动开发——GPIO知识点&#xff08;一&#xff09;_daisy.skye的博客-CSDN博客基于RK3568的Linux驱动开发—— GPIO知识点&#xff08;二&#…

【H5】盘点HTML5新特性

html5总的来说比html4多了十个新特性&#xff0c;但其不支持ie8及ie8以下版本的浏览器 文章目录 一、语义标签二、增强型表单三、音频和视频四、Canvas绘图五、SVG绘图六、地理定位七、拖放API八、Web Worker九、Web Storage十、WebSocket 一、语义标签 html5语义标签&#x…

Maven的安装与配置(包含所有细节)

一、idea版本和maven配对 这里是很多新手都会遇到的大坑&#xff0c;一定要先将自己的idea版本和maven进行版本配配对。 Maven3.6.3版本兼容问题 注意&#xff1a;针对一些老项目 还是尽量采用 3.6.3版本&#xff0c;针对idea各个版本的兼容性就很兼容 IDEA 2022 兼容maven 3.8…

肠道重要菌属——埃希氏菌属 (Escherichia), 肠道炎症和生态失调相关

谷禾健康 —变形菌门 —γ变形菌纲 —肠杆菌目 —肠杆菌科 —埃希氏菌属 埃希氏菌属 (Escherichia)&#xff0c;是一种常见的细菌。其中最著名的种是大肠杆菌&#xff08;Escherichia coli&#xff09;&#xff0c;大肠杆菌是一种厌氧菌&#xff0c;通常生活在人和动物的肠道中…

echarts 日常设计感图表

饼图 pieChart(id) {const data {value:100,type:aaa}let angle 0; //角度&#xff0c;用来做简单的动画效果的let count echarts.init(document.getElementById(id));let option {title: [{text: "{a|" data.value "}{c|%}",x: "center"…

super父类 事物

一个没有事物的方法。 调用他的父类里有事物的方法。 无论this 和 super 都会让父类事物方法没有事物。 如果写了super.class 文件里面&#xff0c;就是super调用。 如果没写&#xff0c;就是this调用&#xff0c;坑爹 测试&#xff0c;把父类注入&#xff0c;事物才生效。

Redis——特性介绍与应用场景

Redis特性介绍 In-memory data structrues 众所周知&#xff0c;MySQL是一种关系型数据库&#xff0c;其通过表的结构存储数据&#xff0c;就类似于建立了一个excel表格来存储数据。但是像视频这类数据并不适合存储在关系型数据库中&#xff0c;因此存在非关系型数据库——通…