当前位置:首页 > 技术 > 正文内容

基于 Python 实现 JMeter 测试用例动态构建与执行

访客 技术 2026年5月26日 3

自动化性能测试背景

在持续集成环境中,手动维护 JMeter 测试计划(.jmx)效率低下且容易出错。通过 Python 脚本动态生成测试文件并结合命令行工具执行,可以实现测试流程的标准化与自动化。本方案主要包含两个核心环节:利用模板引擎渲染 JMX 文件,以及通过系统调用驱动 JMeter 进行无界面压测。

JMeter 环境配置

首先需确保本地已安装 Apache JMeter,并将 bin 目录下的可执行文件加入环境变量路径,以便在任意目录下直接调用。若未全局配置,可在代码中指定完整路径。常用的执行模式为非 GUI 模式(Non-GUI),这适用于 CI/CD 流水线。

测试数据与模板分离设计

.jmx 本质上是遵循特定规范的 XML 文档。为了灵活管理参数,我们将静态结构定义为 Jinja2 模板,将动态参数存储在 YAML 配置文件中。这种分离降低了硬编码带来的耦合度。

1. 配置数据结构示例

定义一个包含场景描述、线程组、采样器及校验逻辑的数据文件 test_config.yaml

project_info:
  name: 电商订单接口压测
  desc: 验证高并发下单稳定性
global_settings:
  dns_hosts:
    - domain: api.example.com
      ip: 192.168.1.100
  cookies_policy: true
test_scenarios:
  - id: tg_01
    name: 登录模块
    concurrency: 100
    ramp_up: 60
    enable_schedule: yes
    duration_sec: 300
    requests:
      - action: login
        method: POST
        url: /api/auth/login
        payload_type: json
        body: '{"username": "${user}", "password": "${pwd}"}'
        assert_rules:
          - field: response_body
            pattern: '"status":"ok"'
  - id: tg_02
    name: 商品查询
    concurrency: 50
    ramp_up: 30
    requests:
      - action: query_goods
        method: GET
        url: /api/product/list
        params: {'page': 1, 'size': 20}

2. 模板引擎渲染逻辑

JMX 模板 jmeter_template.xml 需要保留 JMeter 标准标签结构,但在变量区域嵌入 Jinja2 语法。注意 XML 节点如 <hashTree> 不能随意更改,但内容属性可由变量填充。

<jmeterTestPlan version="1.2" properties="5.0">
<hashTree>
  <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="{{ project_info.name }}">
    <stringProp name="TestPlan.comments">{{ project_info.desc }}</stringProp>
    <boolProp name="TestPlan.functional_mode">false</boolProp>
  </TestPlan>
  <hashTree>
    {% if global_settings.dns_hosts %}
    <DNSCacheManager guiclass="DNSCachePanel" testclass="DNSCacheManager" testname="DNS 缓存">
       <collectionProp name="DNSCacheManager.hosts">
         {% for host in global_settings.dns_hosts %}
         <elementProp name="{{ host.domain }}" elementType="StaticHost">
           <stringProp name="StaticHost.Name">{{ host.domain }}</stringProp>
           <stringProp name="StaticHost.Address">{{ host.ip }}</stringProp>
         </elementProp>
         {% endfor %}
       </collectionProp>
    </DNSCacheManager>
    <hashTree/>
    {% endif %}
    
    <!-- 线程组循环 -->
    {% for scenario in test_scenarios %}
    <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="{{ scenario.name }}">
       <stringProp name="ThreadGroup.num_threads">{{ scenario.concurrency }}</stringProp>
       <stringProp name="ThreadGroup.ramp_time">{{ scenario.ramp_up }}</stringProp>
       {% if scenario.enable_schedule == 'yes' %}
       <boolProp name="ThreadGroup.scheduler">true</boolProp>
       <stringProp name="ThreadGroup.duration">{{ scenario.duration_sec }}</stringProp>
       {% else %}
       <boolProp name="ThreadGroup.scheduler">false</boolProp>
       </intProp name="LoopController.loops">-1</intProp>
       {% endif %}
    </ThreadGroup>
    <hashTree>
      {% for req in scenario.requests %}
      <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="{{ req.action }}">
        <stringProp name="HTTPSampler.method">{{ req.method }}</stringProp>
        <stringProp name="HTTPSampler.path">{{ req.url }}</stringProp>
        {% if req.method == 'POST' %}
        <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
           <collectionProp name="Arguments.arguments">
             <elementProp name="" elementType="HTTPArgument">
               <stringProp name="Argument.value">{{ req.body | default("") }}</stringProp>
             </elementProp>
           </collectionProp>
        </elementProp>
        {% endif %}
      </HTTPSamplerProxy>
      <hashTree>
        {% if req.assert_rules %}
        <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion">
           <collectionProp name="Asserion.test_strings">
             {% for rule in req.assert_rules %}
             <stringProp name="97">{{ rule.pattern }}</stringProp>
             {% endfor %}
           </collectionProp>
           <intProp name="Assertion.test_type">16</intProp>
        </ResponseAssertion>
        <hashTree/>
        {% endif %}
      </hashTree>
      {% endfor %}
    </hashTree>
    {% endfor %}
    <!-- 监听器:聚合报告 -->
    <ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="聚合报告">
      <objProp><name>saveConfig</name><value class="SampleSaveConfiguration"/></objProp>
    </ResultCollector>
    <hashTree/>
  </hashTree>
</hashTree>
</jmeterTestPlan>

Python 自动化编排脚本

以下脚本实现了加载配置、生成 JMX 文件、启动压测任务以及生成可视化报告的完整流程。相比直接使用 os.system,推荐使用 subprocess 以便更精细地控制进程流和错误捕获。

import os
import yaml
import subprocess
import jinja2
from pathlib import Path

def load_configuration(config_path):
    """读取 YAML 配置文件"""
    with open(config_path, 'r', encoding='utf-8') as stream:
        return yaml.safe_load(stream)

def generate_jmx(template_path, data_source, output_path):
    """使用 Jinja2 渲染生成 JMX 文件"""
    env = jinja2.Environment(loader=jinja2.FileSystemLoader(Path(template_path).parent))
    template = env.get_template(Path(template_path).name)
    
    # 渲染内容
    rendered_xml = template.render(data_source)
    
    # 写入目标文件
    with open(output_path, 'w', encoding='utf-8') as f:
        f.write(rendered_xml)
    
    print(f"[Success] Test plan generated at {output_path}")

def run_pressure_test(jmx_file, log_suffix=""):
    """执行非 GUI 模式测试并生成结果文件"""
    result_log = f"result_{log_suffix}.jtl"
    server_log = f"server_{log_suffix}.log"
    html_report_dir = f"report_{log_suffix}"
    
    # 清理旧报告
    if os.path.exists(html_report_dir):
        subprocess.run(["rm", "-rf", html_report_dir])
        
    # 执行压测命令:-n(非 GUI) -t(test file) -l(log output) -j(server log)
    cmd_run = [
        "jmeter", 
        "-n", "-t", jmx_file, 
        "-l", result_log, 
        "-j", server_log
    ]
    
    print("[Starting] Running JMeter engine...")
    proc = subprocess.run(cmd_run, capture_output=True, text=True)
    
    if proc.returncode != 0:
        print(f"[Error] JMeter failed with code {proc.returncode}")
        print(proc.stderr)
        return False

    # 生成 HTML 报告:-g(source jtl) -o(output dir)
    cmd_report = ["jmeter", "-g", result_log, "-o", html_report_dir]
    subprocess.run(cmd_report, check=True)
    
    print(f"[Finished] Report available in {html_report_dir}/index.html")
    return True

if __name__ == "__main__":
    cfg_path = "test_config.yaml"
    tpl_path = "jmeter_template.xml"
    target_jmx = "auto_gen_plan.jmx"
    
    # 1. 准备数据
    config_data = load_configuration(cfg_path)
    
    # 2. 生成脚本
    generate_jmx(tpl_path, config_data, target_jmx)
    
    # 3. 执行任务
    run_pressure_test(target_jmx)

扩展与优化建议

在实际生产落地过程中,仅生成和执行是不够的。通常需要配合 Ansible 或 Fabric 实现多台机器分发测试脚本,通过 Paramiko 远程收集 .jtl 日志文件进行统一分析。此外,考虑到安全性,敏感信息(如 Token、密钥)建议不直接写在 YAML 中,而是通过环境变量注入或在运行时由脚本动态拼接。对于复杂的业务链路,还可以结合 Python 的断言库对生成的响应数据进行二次校验,增强测试结果的可靠性。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。