大家都知道,目前JMeter本身只支持批量执行单个jmx脚本下所有的线程组,但并不支持批量执行多个jmx脚本。往往我们在回归测试的时候都希望能一键执行所有脚本,执行完毕只查看测试结果即可,方便省事。现在网上能百度到的解决方案多数为借助ant或maven等构件工具,但上述那些方案我个人并不喜欢且测试报告也不美观,为了解决上述问题,我们可以利用前面章节介绍的【ENV Data Set】和【Local HTML Report】插件的特性再配合Python的一个批量执行程序就能实现JMeter脚本的批量执行。
Python是使用3.6版本编写的,下面贴出核心代码,完整代码请到Github上查看:https://github.com/YeKelvin/testutil-python。
jmeter.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Author : KelvinYe
# jmeter.py
import os
import sys
import traceback
from os.path import isdir
from subprocess import Popen, PIPE, STDOUT
from testutil.common import config
from testutil.common.number_util import decimal_to_percentage
from testutil.common.time_util import current_time_as_s, seconds_convert_to_hms, current_time_as_dirname
from testutil.file_io.path_util import (path_transform_reportname, get_script_list,
get_abspath_by_scriptname, isjmx,
get_abspath_by_relative_path, get_file_list_by_env)
class Jmeter:
"""Jmeter类
"""
def __init__(self, jmeter_bin: str, env: str, reportname: str, is_append: bool):
self.jmeter_path = os.path.join(jmeter_bin, 'jmeter')
self.options = rf' -JconfigName="{env}" -JreportName="{reportname}" -JisAppend="{is_append}" -n -t '
self.__command = self.jmeter_path + self.options
def execute(self, jmx_abspath: str) -> None:
"""根据路径执行jmeter脚本
Args:
jmx_abspath: 脚本绝对路径
Returns: None
"""
command = self.__command + jmx_abspath
print(f'Commond:[{command}]\n')
popen = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True, universal_newlines=True)
while popen.poll() is None: # 检查子进程是否结束
line = popen.stdout.readline()
line = line.strip()
if line:
print(line)
if popen.returncode == 0:
print('Script execution success.\n')
else:
print('Script execution failed.\n')
def run(env: str, path: str) -> None:
jmeterbin = config.get('jmeter', 'bin')
workspace = config.get('jmeter', 'workspace')
reportname = 'regression-testing-report.html'
is_append = 'true'
regression_parent = os.path.join(workspace, 'regression-testing')
currenttime = current_time_as_dirname()
abspath = None
jmx_list = []
if path == "":
# 如 path 为空,则执行 workspace/regression-testing回归目录 下所有jmx脚本
jmx_list = get_script_list(regression_parent)
else:
# 如 path 非空,则先判断 path 是目录还是脚本
abspath = get_abspath_by_relative_path(workspace, path)
if isdir(abspath):
reportname = path_transform_reportname(path)
jmx_list = get_script_list(abspath)
elif isjmx(abspath):
reportname = os.path.split(abspath)[1][:-4]
jmx_list.append(get_abspath_by_scriptname(workspace, path))
else:
print(f'" {workspace} "下,不存在 {abspath} 此目录或脚本')
sys.exit()
# 待执行脚本列表非空校验
if len(jmx_list) == 0 or jmx_list[0] == '':
print(f'" {workspace} "下,目录或脚本不存在')
sys.exit()
# 排除脚本名含 skip 的脚本
del_skip_script(jmx_list)
# 统计总脚本数
script_number = len(jmx_list)
# reportname = rf'[{reportname}]{currenttime}.html'
reportname = rf'[{currenttime}]{reportname}.html'
os.chdir(jmeterbin) # 设置当前工作路径为jmeter\bin
jmeter = Jmeter(jmeterbin, env, reportname, is_append)
print(f'JmeterBin:[{jmeterbin}]')
print(f'Workspace:[{workspace}]')
print(f'脚本读取路径:[{abspath}]')
print(f'总脚本数:[{script_number}]\n')
# 用于统计完成脚本数
completed_number = 0
# 记录开始时间
starttime = current_time_as_s()
for script in jmx_list:
current_starttime = current_time_as_s()
jmeter.execute(script)
current_elapsed_time = current_time_as_s() - current_starttime
completed_number += 1
print(f'当前脚本耗时:[{seconds_convert_to_hms(current_elapsed_time)}]')
print(f'已完成脚本数:[{completed_number}],剩余脚本数:[{script_number - completed_number}],'
f'当前总进度:[{decimal_to_percentage(completed_number/script_number)}]\n')
# 统计总耗时
total_elapsed_time = current_time_as_s() - starttime
print(f'总耗时:[{seconds_convert_to_hms(total_elapsed_time)}]')
reportpath = os.path.join(config.get('jmeter', 'home'), 'htmlreport', reportname)
print(f'所有脚本执行完毕,详细数据请查看测试报告,报告路径:[{reportpath}]\n')
def del_skip_script(jmx_list: list):
# 待删列表
del_list = []
# 查找脚本名含 skip 的脚本列表索引
for index, jmx in enumerate(jmx_list):
if 'skip' in jmx:
del_list.append(index)
# 根据待删列表删除元素
for index in del_list:
del jmx_list[index]
def get_env_list() -> list:
"""打印测试环境列表
"""
config_path = os.path.join(config.get('jmeter', 'home'), 'config')
return get_file_list_by_env(config_path)
if __name__ == '__main__':
env_list = get_env_list()
print(f'支持的测试环境: {env_list}')
while True:
env = input('请输入以上测试环境配置之一的名称:')
if env in env_list:
break
print('不存在此测试环境配置,请重新输入。')
path = input('请输入需执行的目录或脚本(相对路径,脚本需后缀):')
try:
run(env, path)
except Exception as e:
traceback.print_exc()
finally:
input('按任意键退出')
说明一下,我的jmx脚本全部都是放在workspace目录下的,版本测试脚本放在version-testing目录下,待脚本测试完毕后再放入regression-testing目录下,这些目录设计将在后续章节中详细说明。
path_util.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Author : KelvinYe
# path_util.py
import os
def get_script_list(dirpath):
"""
返回目录及子目录下所有的jmx脚本
"""
jmxs = []
for parent, dirnames, filenames in os.walk(dirpath):
for filename in filenames:
if filename.endswith('jmx'):
jmxs.append(os.path.join(parent, filename))
return jmxs
def path_transform_reportname(path: str):
"""
根据路径转换为测试报告名称
"""
# 因path值为人工输入,故不使用os.path.sep
if path[0] == '/' or path[0] == '\\':
path = path[1:]
return path.replace('/', '.').replace('\\', '.')
def get_abspath_by_scriptname(dirpath, scriptname):
"""
返回指定脚本名的绝对路径
"""
abspath = ''
for parent, dirnames, filenames in os.walk(dirpath):
for filename in filenames:
if scriptname == filename:
abspath = os.path.join(parent, filename)
break
return abspath
def isjmx(filename):
"""
根据文件名(入参含后缀)判断文件后缀是否为 .xml
"""
return os.path.splitext(filename)[-1] == '.jmx' if True else False
def get_abspath_by_relative_path(dirpath, relative_path: str):
"""
根据 尾部部分路径 模糊搜索,返回第一个匹配成功的尾部相对路径的绝对路径
"""
if relative_path.endswith('/') or relative_path.endswith('\\'):
relative_path = relative_path[:-1]
relative_path = os.path.normpath(relative_path)
abspath = ''
for parent, dirnames, filenames in os.walk(dirpath):
for dirname in dirnames:
if os.path.join(parent, dirname).endswith(relative_path):
abspath = os.path.join(parent, dirname)
break
for filename in filenames:
if os.path.join(parent, filename).endswith(relative_path):
abspath = os.path.join(parent, filename)
break
return abspath
def get_file_list_by_env(dirpath):
env_list = []
for file in os.listdir(dirpath):
if file.endswith('.env'):
env_list.append(file)
return env_list
time_util.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Author : KelvinYe
# time_util.py
from _datetime import datetime
from time import time
def current_time_as_s():
"""获取秒级时间戳,用于计算秒级耗时
"""
return int(time())
def seconds_convert_to_hms(seconds: int) -> str:
"""秒数转换为时分秒
Args:
seconds: 秒数
Returns: 时分秒
"""
m, s = divmod(seconds, 60)
h, m = divmod(m, 60)
return '%02dh:%02dm:%02ds' % (h, m, s)
def current_time_as_dirname():
return datetime.now().strftime('%Y.%m.%d %H.%M.%S')
number_util.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Author : KelvinYe
# number_util.py
def decimal_to_percentage(decimal: float) -> str:
"""小数转百分比
Args:
decimal: 小数
Returns: 百分比值
"""
return '%.2f%%' % (decimal * 100)
此外还需要在resources/config.ini下配置一些路径:
[jmeter]
home = F:\Jmeter\apache-jmeter-3.1
bin = F:\Jmeter\apache-jmeter-3.1\bin
workspace = F:\Jmeter\works\
在Github上clone项目下来后,配置好resources/config.ini配置文件,然后直接双击testutil-python/testutil/pymeter/jmeter.py就能运行了,然后按照提示输入测试环境和脚本所在目录就会开始执行测试了。
这里运行Python的时候可能会提示找不到模块,有一个一劳永逸的解决方法:
然后就能愉快的执行一键回归测试啦。