基于Jmeter的Dubbo API自动化测试实战(八)

大家都知道,目前JMeter本身只支持批量执行单个jmx脚本下所有的线程组,但并不支持批量执行多个jmx脚本。往往我们在回归测试的时候都希望能一键执行所有脚本,执行完毕只查看测试结果即可,方便省事。现在网上能百度到的解决方案多数为借助ant或maven等构件工具,但上述那些方案我个人并不喜欢且测试报告也不美观,为了解决上述问题,我们可以利用前面章节介绍的【ENV Data Set】和【Local HTML Report】插件的特性再配合Python的一个批量执行程序就能实现JMeter脚本的批量执行。

一、Python批量执行程序

  1. 递归遍历所有jmx脚本并获取脚本的绝对路径
  2. 循环执行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的时候可能会提示找不到模块,有一个一劳永逸的解决方法:

  1. 在PythonHome根目录下新建mypkpath.pth文件
  2. 编辑mypkpath.pth,添加testutil-python根目录路径

然后就能愉快的执行一键回归测试啦。

基于Jmeter的Dubbo API自动化测试实战(八)_第1张图片

 

基于Jmeter的Dubbo API自动化测试实战(八)_第2张图片

 

 

 

你可能感兴趣的:(JMeter)