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

目录

背景介绍

实现思路

把脚本数据读出,使用正则表达式(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 BeautifulSoupclass 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")@staticmethoddef _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:passelse:_loops = self.soup.find("intProp", {"name": {"LoopController.loops"}})return _loops@staticmethoddef get_time():return time.strftime("%Y-%m-%d@%X", time.localtime())def get_bean_shell(self):_str = self._bean_shell().stringlogger.info("bean_shell: " + _str)return _strdef set_bean_shell(self, new_bean_shell):old_bean_shell = self._bean_shell()old_bean_shell.string = new_bean_shelldef get_ramp_time(self):_str = self._ramp_time().stringlogger.info("ramp_time: " + _str)return _str@check_numdef set_ramp_time(self, num):loops = self._ramp_time()loops.string = numdef get_loops(self):_str = self._loops().stringlogger.info("loops: " + _str)return _str@check_numdef set_loops(self, num):""":param num: -1 为一直循环,其他为具体循环次数:return:"""loops = self._loops()loops.string = numdef get_argument(self):_str = self._argument().stringlogger.info("argument: " + _str)return _strdef set_argument(self, **kwargs):"""设置请求参数(JSON,传入字典):param kwargs::return:"""param = self._argument()param.string = str(kwargs)def get_thread_num(self):_str = self._theard_num().stringlogger.info("thread_num: " + _str)return _str@check_numdef 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 = keyheaders[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 = valuedef 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.argvlen_argvs = len(argvs) - 1if len_argvs == 0:passelif 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 redef 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.mzph.cn/news/27539.shtml

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

相关文章

Air32 | 合宙Air001单片机内部FLASH读写示例

Air32 | 合宙Air001单片机内部FLASH读写示例 代码已经通过测试&#xff0c;开发环境KEIL-MDK 5.36。 测试代码 void FLASH_RdWrTest(void) {uint32_t Address;uint32_t PageReadBuffer[FLASH_PAGE_SIZE >> 2];uint32_t PageWriteBuffer[FLASH_PAGE_SIZE >> 2];mem…

b站视频标题的获取(xpath、jsonpath的一个简单应用)

目录 1.目的2.代码的演示 注&#xff1a;该篇文章为本人原创&#xff0c;由于本人学习有限&#xff0c;若有错误或者笔误或者有问题&#xff0c;欢迎大家进行批评指正&#xff0c;谢谢。 1.目的 在b站大学上&#xff0c;为了更好的写笔记&#xff0c;本人根据学到的Python(即Py…

springboot家政服务管理系统java家务保姆资源 jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 springboot家政服务管理系统 系统1权限&#xff1a;管…

挑战Open AI!!!马斯克宣布成立xAI.

北京时间7月13日凌晨&#xff0c;马斯克在Twitter上宣布&#xff1a;“xAI正式成立&#xff0c;去了解现实。”马斯克表示&#xff0c;推出xAI的原因是想要“了解宇宙的真实本质”。Ghat GPT横空出世已有半年&#xff0c;国内外“百模大战”愈演愈烈&#xff0c;AI大模型的现状…

nginx部署以及反向代理多域名实现HTTPS访问

nginx部署以及反向代理多域名实现 1.nginx部署 1.1 编写nginx部署文件 docker-compose.yml version: 3 services: nginx:restart: always image: nginx:1.20container_name: nginx-mainports:- 80:80- 443:443volumes: # 基础配置- /opt/nginx_main/nginx-info/nginx.conf:/…

【C++】STL——stack和queue的模拟实现、空间适配器、deque的介绍、增删查改函数的简单实现

文章目录 1.deque的简单介绍2.模拟实现stack3.模拟实现queue 1.deque的简单介绍 deque的介绍文档 deque(双端队列)&#xff1a;是一种双开口的"连续"空间的数据结构&#xff0c;双开口的含义是&#xff1a;可以在头尾两端进行插入和删除操作&#xff0c;且时间复杂度…

ElastAlert通过飞书机器人发送报警通知

前言 公司采用ELK架构搜集业务系统的运行日志&#xff0c;以前开发人员只有在业务出现问题的时候&#xff0c;才会去kibana上进行日志搜索操作&#xff0c;每次都是被用户告知系统出问题了&#xff0c;这简直是被啪啪打脸~ 于是痛定思痛&#xff0c;决定主动出击&#xff0c;…

(树) 剑指 Offer 54. 二叉搜索树的第k大节点 ——【Leetcode每日一题】

❓剑指 Offer 54. 二叉搜索树的第k大节点 难度&#xff1a;简单 给定一棵二叉搜索树&#xff0c;请找出其中第 k 大的节点的值。 示例 1: 输入: root [3,1,4,null,2], k 13/ \1 4\2 输出: 4示例 2: 输入: root [5,3,6,2,4,null,null,1], k 35/ \3 6/ \2 4/1 输出…

Nacos安装(centos7)

安装版本2.0.2&#xff0c;下载地址 Release 2.0.2 (Jun 11th, 2021) alibaba/nacos GitHub 安装 启动 到bin目录下 sh startup.sh -m standalone 访问http://192.168.23.130:8848/nacos 输入nacos/nacos登入

opencv-32 图像平滑处理-高斯滤波cv2.GaussianBlur()

在进行均值滤波和方框滤波时&#xff0c;其邻域内每个像素的权重是相等的。在高斯滤波中&#xff0c;会将中心点的权重值加大&#xff0c;远离中心点的权重值减小&#xff0c;在此基础上计算邻域内各个像素值不同权重 的和。 基本原理 在高斯滤波中&#xff0c;卷积核中的值不…

研究人员发现特斯拉汽车能被越狱,可免费解锁付费功能

Bleeping Computer 网站披露&#xff0c;柏林工业大学&#xff08;Technical University of Berlin&#xff09;的研究人员开发出一种新技术&#xff0c;可以破解特斯拉近期推出所有车型上使用的基于 AMD 的信息娱乐系统&#xff0c;并使其运行包括付费项目在内的任何软件。 实…

【Spring】创建一个Spring项目与Bean对象的存储

目录 一、创建Spring项目 1、创建Maven项目 2、配置maven国内源 3、引入spring依赖 4、添加启动类 二、将Bean对象存储到Spring&#xff08;IoC容器&#xff09; 1、创建Bean对象 2、将Bean存储到spring&#xff08;容器&#xff09;中 3、获取Bean对象 3.1、Applicatio…

黑马机器学习day1

1.sklearn数据集 sklearn.datasets datasets.load_*() 获取小规模的数据集 datasets.fetch_*(data_homeNone) 获取大规模数据集 函数的第一个参数是data_home,标识数据集下载目录&#xff0c;默认/scikit_learn_data/ 1.1sklearn小数据集 sklearn.da…

3dmax好用插件CG Magic专业版上线,批量渲染,智能优化

CG Magic是一款基于3ds Max深度开发的智能化辅助插件&#xff0c;上千项实用功能&#xff0c;降低渲染时长&#xff0c;节省时间和精力&#xff0c;最大程度简化工作流程&#xff0c;助力高效完成创作。 CG MAGIC是基于3ds Max深度开发的智能化辅助设计插件&#xff0c;目前有两…

JavaWeb 速通Cookie

目录 一、关于base标签 1.引入 : 2.介绍 : 3.实例 : 4.细节 : 二、Cookie的引入 1.会话技术 : 1 什么是会话技术&#xff1f; 2 会话技术用于解决什么问题&#xff1f; 2.Cookie介绍 1 Cookie有什么用&#xff1f; 2 Cookie通讯机制 三、Cookie的基本使用 1.创建Cookie…

Pandas 的Merge函数详解

在日常工作中&#xff0c;我们可能会从多个数据集中获取数据&#xff0c;并且希望合并两个或多个不同的数据集。这时就可以使用Pandas包中的Merge函数。在本文中&#xff0c;我们将介绍用于合并数据的三个函数 merge、 merge_ordered、 merge_asofmerge merge函数是Pandas中…

如何实现对主机的立体监控?

主机监控是保证系统稳定性和性能的重要环节之一&#xff0c;那应该如何实现对主机的立体监控&#xff1f; 本期EasyOps产品使用最佳实践&#xff0c;我们将为您揭晓&#xff1a; 主机应该如何分组和管理&#xff1f; 主机监控应该关注哪些关键性指标&#xff1f; 背 景 通…

MySQL建表和增添改查

1.创建一个名为mydb的数据库 mysql> show database mydb; 查询 mysql> show database mydb; 2.创建一个学生信息表 mysql> create table mydb.student_informtion( -> student_id int UNSIGNED NOT NULL PRIMARY KEY, //非空&#xff08;不允许为空&#xff0…

FastAPI(七)应用配置

目录 一、在apps下新建文件夹config 二、新建配置文件app_conf.py 一、在apps下新建文件夹config 二、新建配置文件app_conf.py from functools import lru_cachefrom pydantic.v1 import BaseSettingsclass AppConfig(BaseSettings):app_name: str "Windows10 插件&qu…

WGS_1984_UTM、WGS_1984_Mercator坐标转化为经纬度坐标python

1、遥感影像的PROJECTION有哪些 遥感影像常见的投影类型有很多&#xff0c;具体选择哪种投影方式取决于数据的特性和使用需求。以下列举了一些常见的遥感影像投影类型&#xff1a; UTM (Universal Transverse Mercator) 投影&#xff1a;最常见的投影类型之一&#xff0c;将地…