接口测试:JMeter(四)

CSV数据文件设置

在config中配置CSV data set config,如果同一个接口需要多组参数,可以建一个csv文件,目录结构如下

循环控制器: 循环次数,和csv中有效数据行数要一致,这样所有数据就都可以测了
 |----待测接口
 	|----HTTP信息头管理器:value的部分要用csv的变量,如:${username},${id}
 	|----CSV数据文件设置:传入csv文件,配置conding:utf-8需要和文件一致,变量名,用`,`分开

最佳实践:可以在csv中加一个title的字段,表示当前行的测试数据类型,比如,登录场景,第一行的title可以是:正确的用户名密码,第二行是:正确的用户名错误的密码,......类似这样,让后将title这个也作为变量暴露,然后再request的title进行引用,用户登录测试-${title}

加密接口如何测试

测试加密接口是 JMeter 中比较常见的需求,特别是涉及安全要求的 API。下面详细讲解各种加密接口的测试方法。


一、加密接口的常见类型

加密类型 特点 常见场景
HTTPS/SSL 传输层加密 所有安全要求的 Web API
请求参数加密 业务数据加密 登录密码、支付信息、敏感数据
签名验证 防篡改机制 API 签名、防重放攻击
Token 认证 身份验证加密 JWT、OAuth 2.0
双向加密 客户端证书 银行、支付等高风险场景

二、HTTPS/SSL 加密接口测试

1. 配置 HTTP 请求使用 HTTPS

简单配置:

HTTP 请求
├── 协议: https
├── 服务器名称: api.example.com
├── 端口: 443
└── 路径: /api/users

2. 处理 SSL 证书问题

跳过证书验证(开发/测试环境):

  • HTTP 请求 中勾选:
    • Use keepalive
    • Use multipart/form-data
    • 从HTML文件获取所有内含的资源
    • 重要:在 高级 选项卡中:
      • 实现:选择 HttpClient4
      • 勾选 Use keepalive

通过系统属性禁用 SSL 验证:

在线程组最前面添加 JSR223 预处理器

// 禁用 SSL 证书验证(测试环境用)
import javax.net.ssl.*
import java.security.cert.X509Certificate

// 创建信任所有证书的 TrustManager
TrustManager[] trustAllCerts = [
    new X509TrustManager() {
        public X509Certificate[] getAcceptedIssuers() { null }
        public void checkClientTrusted(X509Certificate[] certs, String authType) { }
        public void checkServerTrusted(X509Certificate[] certs, String authType) { }
    }
] as TrustManager[]

// 应用配置
SSLContext sc = SSLContext.getInstance("SSL")
sc.init(null, trustAllCerts, new java.security.SecureRandom())
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory())

// 设置主机名验证器
HttpsURLConnection.setDefaultHostnameVerifier { hostname, session -> true }

三、请求参数加密测试

1. 使用 JSR223 预处理器动态加密

示例:AES 加密参数

import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import java.util.Base64

// 加密函数
def encryptAES(String data, String key) {
    SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "AES")
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
    cipher.init(Cipher.ENCRYPT_MODE, secretKey)
    byte[] encrypted = cipher.doFinal(data.getBytes("UTF-8"))
    return Base64.getEncoder().encodeToString(encrypted)
}

// 要加密的数据
def rawData = '{"username":"testuser","password":"123456"}'
def secretKey = "0123456789abcdef" // 16位密钥

// 执行加密
def encryptedData = encryptAES(rawData, secretKey)

// 保存到变量供请求使用
vars.put("encryptedData", encryptedData)
log.info("加密后的数据: " + encryptedData)

在 HTTP 请求中使用:

HTTP 请求
├── 方法: POST
├── 路径: /api/encrypted/login
└── 消息体数据: 
    {
        "encrypted_data": "${encryptedData}"
    }

2. RSA 非对称加密示例

import javax.crypto.Cipher
import java.security.KeyFactory
import java.security.PublicKey
import java.security.spec.X509EncodedKeySpec
import java.util.Base64

// RSA 公钥加密
def encryptRSA(String data, String publicKeyStr) {
    // 处理公钥字符串(去掉头尾标记)
    publicKeyStr = publicKeyStr.replace("-----BEGIN PUBLIC KEY-----", "")
                              .replace("-----END PUBLIC KEY-----", "")
                              .replaceAll("\\s", "")
    
    byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr)
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes)
    KeyFactory keyFactory = KeyFactory.getInstance("RSA")
    PublicKey publicKey = keyFactory.generatePublic(keySpec)
    
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
    cipher.init(Cipher.ENCRYPT_MODE, publicKey)
    byte[] encrypted = cipher.doFinal(data.getBytes("UTF-8"))
    
    return Base64.getEncoder().encodeToString(encrypted)
}

// 使用示例
def publicKey = """
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
"""

def rawData = '{"amount":100.00,"orderNo":"ORD123456"}'
def encrypted = encryptRSA(rawData, publicKey)
vars.put("encryptedRequest", encrypted)

四、签名验证接口测试

1. 生成 API 签名

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.security.MessageDigest

// 生成 MD5 签名
def generateMD5Signature(Map params, String secret) {
    // 1. 参数排序并拼接
    def sortedParams = params.sort { it.key }
    def queryString = sortedParams.collect { "${it.key}=${it.value}" }.join("&")
    
    // 2. 拼接密钥
    def stringToSign = queryString + "&secret=" + secret
    
    // 3. MD5 加密
    MessageDigest md = MessageDigest.getInstance("MD5")
    byte[] digest = md.digest(stringToSign.getBytes("UTF-8"))
    
    // 4. 转为16进制
    return digest.collect { String.format("%02x", it) }.join("")
}

// 生成 HMAC-SHA256 签名
def generateHMACSHA256(String data, String secret) {
    Mac mac = Mac.getInstance("HmacSHA256")
    SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256")
    mac.init(secretKey)
    byte[] digest = mac.doFinal(data.getBytes("UTF-8"))
    return digest.collect { String.format("%02x", it) }.join("")
}

// 使用示例
def params = [
    "appId": "1001",
    "timestamp": System.currentTimeMillis(),
    "nonce": UUID.randomUUID().toString().substring(0, 8)
]

def secretKey = "your_secret_key"
def signature = generateMD5Signature(params, secretKey)

// 保存变量
params.each { k, v -> vars.put(k, v) }
vars.put("signature", signature)

2. 在 HTTP 请求中使用签名

HTTP 请求
├── 方法: POST
├── 路径: /api/signed/operation
├── 参数:
│   ├── appId: ${appId}
│   ├── timestamp: ${timestamp}
│   ├── nonce: ${nonce}
│   └── signature: ${signature}
└── 消息体数据: { ... }

五、JWT Token 认证测试

1. 生成 JWT Token

import java.util.Base64

// 简单的 JWT 生成(实际项目应使用专业库)
def generateSimpleJWT(Map payload, String secret) {
    def header = '{"alg":"HS256","typ":"JWT"}'
    def payloadJson = new groovy.json.JsonBuilder(payload).toString()
    
    // Base64 URL 编码
    def headerBase64 = Base64.getUrlEncoder().encodeToString(header.getBytes())
    def payloadBase64 = Base64.getUrlEncoder().encodeToString(payloadJson.getBytes())
    
    def signingInput = headerBase64 + "." + payloadBase64
    
    // 这里应该使用 HMAC-SHA256 生成签名(简化示例)
    def signature = "generated_signature_here" 
    
    return signingInput + "." + signature
}

// 使用示例
def payload = [
    "userId": 12345,
    "username": "testuser",
    "exp": System.currentTimeMillis() + 3600000 // 1小时后过期
]

def jwtToken = generateSimpleJWT(payload, "your-secret")
vars.put("jwtToken", jwtToken)

2. 在请求头中使用 JWT

HTTP 信息头管理器
├── Content-Type: application/json
└── Authorization: Bearer ${jwtToken}

六、完整测试示例:加密登录接口

测试场景:AES加密的登录接口

测试计划
├── 用户定义的变量
│   ├── baseURL=https://api.example.com
│   ├── aesKey=0123456789abcdef
│   └── apiSecret=test_secret_123
├── 线程组
│   ├── JSR223 预处理器 (生成签名和加密数据)
│   ├── HTTP 信息头管理器
│   ├── HTTP 请求 (登录接口)
│   └── JSON 断言 (验证响应)

JSR223 预处理器代码:

import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import java.util.Base64

// 1. AES 加密函数
def encryptAES(String data, String key) {
    SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "AES")
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
    cipher.init(Cipher.ENCRYPT_MODE, secretKey)
    byte[] encrypted = cipher.doFinal(data.getBytes("UTF-8"))
    return Base64.getEncoder().encodeToString(encrypted)
}

// 2. 准备登录数据
def loginData = [
    username: 'testuser',
    password: 'Test@123',
    timestamp: System.currentTimeMillis()
]

// 3. 生成签名(假设接口需要MD5签名)
def generateSign(Map data, String secret) {
    def sorted = data.sort { it.key }
    def query = sorted.collect { "${it.key}=${it.value}" }.join("&")
    def stringToSign = query + "&secret=" + secret
    
    def md = java.security.MessageDigest.getInstance("MD5")
    byte[] digest = md.digest(stringToSign.getBytes("UTF-8"))
    return digest.collect { String.format("%02x", it) }.join("")
}

// 4. 加密敏感数据
def encryptedPassword = encryptAES(loginData.password, vars.get("aesKey"))

// 5. 生成签名
def signature = generateSign(loginData, vars.get("apiSecret"))

// 6. 保存到变量
vars.put("encryptedPassword", encryptedPassword)
vars.put("timestamp", loginData.timestamp.toString())
vars.put("signature", signature)
vars.put("username", loginData.username)

HTTP 请求配置:

HTTP 请求
├── 方法: POST
├── 协议: https
├── 服务器: api.example.com
├── 路径: /api/v1/login
└── 消息体数据:
    {
        "username": "${username}",
        "password": "${encryptedPassword}",
        "timestamp": "${timestamp}",
        "signature": "${signature}"
    }

七、高级技巧和最佳实践

1. 使用 JMeter 插件支持加密

安装 Custom JMeter Functions 插件,添加内置加密函数支持。

2. 参数化加密密钥

使用 CSV 数据文件 管理不同环境的密钥:

env,baseURL,aesKey,apiSecret
dev,https://dev-api.example.com,dev_key_123,dev_secret_123
test,https://test-api.example.com,test_key_456,test_secret_456
prod,https://api.example.com,prod_key_789,prod_secret_789

3. 错误处理和日志

try {
    // 加密操作
    def result = encryptSensitiveData(params)
    vars.put("encryptedData", result)
} catch (Exception e) {
    log.error("加密失败: " + e.getMessage())
    SampleResult.setSuccessful(false)
    SampleResult.setResponseMessage("加密处理错误: " + e.getMessage())
}

4. 性能考虑

  • 对于复杂的加密操作,考虑在 Setup Thread Group 中预计算
  • 使用 缓存 避免重复加密相同数据
  • 对于高并发测试,评估加密操作对测试机性能的影响

八、常见问题解决

❌ 问题1:SSL证书验证失败

解决:添加证书到 JMeter 信任库,或测试环境暂时禁用验证。

❌ 问题2:加密结果与预期不符

解决:确保加密算法、模式、填充方式与开发文档一致。

❌ 问题3:签名验证失败

解决:检查参数排序规则、编码方式、签名算法是否匹配。

❌ 问题4:性能瓶颈

解决:复杂的加密操作可能成为性能瓶颈,考虑优化或使用更高效的算法。


总结

测试加密接口的关键是在 JMeter 中重现客户端的加密逻辑。通过 JSR223 预处理器 执行加密和签名生成,然后将结果传递给 HTTP 请求。

核心步骤

  1. 分析加密需求(什算法、什么参数)
  2. 在 JSR223 中实现加密逻辑
  3. 将加密结果保存为变量
  4. 在 HTTP 请求中使用这些变量
  5. 验证解密的响应数据

掌握这些技巧,你就能测试各种复杂的加密接口了!

跨线程组

线程组的启动方式

好的,这是一个关于JMeter使用的基础且重要的问题。

直接回答:是的,默认情况下,当你点击工具栏的“启动”(Start)按钮时,测试计划中所有的线程组都会按从上到下的顺序依次执行。


详细解释与注意事项

1. 默认行为:顺序执行

JMeter会按照你在测试计划视图中看到的线程组从上到下的顺序来执行。

  • 示例: 如果你的测试计划包含:
    1. 线程组 A- 模拟用户登录
    2. 线程组 B- 模拟用户浏览商品
    3. 线程组 C- 模拟用户下单
  • 执行顺序: JMeter会先启动并完成线程组 A的所有线程(包括循环),然后再启动线程组 B,最后是线程组 C

2. 如何控制线程组的执行?

如果你不希望所有线程组都运行,或者希望它们并行执行,有以下几种方法:

方法 说明 适用场景
绿色启动按钮 运行所有启用的线程组。 这是最常用的方式。 常规的、顺序的负载测试。
禁用线程组 在测试计划中,右键点击某个线程组 -> 禁用(Disable)。被禁用的线程组名称会变灰,启动测试时将被完全跳过 你只想测试其中一两个场景,暂时不想运行其他线程组。
单独运行线程组 右键点击某个线程组 -> 启动(Start)。此时只有这个被选中的线程组会运行,其他线程组不会执行。 调试某个特定的业务场景,非常方便。

3. 一个重要的高级选项:独立运行每个线程组

在线程组的配置中,有一个关键的选项会影响其行为:

  • 位置: 选中一个线程组,在右侧的配置面板中,勾选 Run Thread Groups consecutively (i.e. one at a time) (依次运行线程组(即一次一个))。
  • 这个选项的作用:
    • 如果勾选(默认): 如上所述,线程组按顺序执行,一个完成后才启动下一个。
    • 如果勾选: 所有线程组会同时启动,并行执行。 这对于模拟不同用户群体在同一时间进行不同操作(如一部分人登录,一部分人查询)的场景非常有用。

最佳实践建议

  1. 清晰命名: 为你的线程组起一个清晰的名字(如“01-用户登录”、“02-商品浏览”),便于管理和识别。
  2. 调试时禁用/单独运行: 在编写和调试测试脚本时,充分利用“禁用”和“右键启动”功能,可以大大提高效率。
  3. 理解执行顺序: 在设计混合场景测试时,一定要清楚线程组的执行顺序(顺序还是并行),以确保测试场景符合真实的业务逻辑。

总结

你的操作 结果
点击顶部的绿色启动按钮 所有已启用的线程组会按顺序(或并行,取决于上述选项)执行。
禁用一个线程组,然后点击绿色启动按钮 只有未禁用的线程组会执行。
右键点击一个线程组,选择 启动 只有这个被选中的线程组会执行。

所以,请根据你的测试目标,灵活运用这些功能来控制JMeter的执行范围。

跨线程组

一、什么时候会用到跨线程组?

跨线程组传值的核心目的是:将一个线程组中产生的数据(例如:登录得到的Token、创建的订单ID)传递给另一个完全独立、可能在不同时间运行的线程组。

它的主要应用场景可以概括为以下两大类:

flowchart TD
    A[需要跨线程组传值的场景] --> B[“顺序依赖型<br>(最常见)”]
    A --> C[“并发共享型<br>(较少见)”]

    B --> B1[场景:先登录后操作]
    B1 --> B2[“线程组A(登录)<br>生成Token”]
    B2 --> B3[“线程组B(业务操作)<br>需要使用Token”]

    C --> C1[场景:共享公共参数]
    C1 --> C2[“线程组A(配置setUp)<br>获取全局配置(如商品ID)”]
    C2 --> C3[“线程组B, C, D...(并发业务)<br>同时使用该配置”]

下面我们来详细解读这些场景:

场景一:顺序依赖型(最常见)

这是最主流的用法,通常与setUp Thread GrouptearDown Thread Group结合使用。

  • 典型例子:登录Token传递
    • 线程组A (setUp Thread Group): 只执行一次,用于全局初始化,比如获取一个一次性的、有有效期的认证Token。
    • 线程组B (普通线程组): 模拟主要业务操作(如查询、下单)。这些操作都需要使用线程组A中获取的Token来构造请求头。
    • 需求: 线程组B需要能拿到线程组A中生成的Token。
  • 典型例子:工作流数据传递
    • 线程组A (创建数据): 模拟用户创建一条订单,生成一个orderId
    • 线程组B (处理数据): 模拟另一个流程(如支付、查询订单详情),需要使用线程组A创建的orderId
    • 需求: 线程组B需要能拿到线程组A中生成的orderId

场景二:并发共享型(较少见)

  • 典型例子:共享配置
    • 一个线程组从一个配置接口获取一些全局参数(如服务器时间、商品列表),然后多个并发线程组同时使用这些参数。虽然这种情况也可以用setUp Thread Group实现,但有时业务逻辑要求这些配置需要动态获取。

核心思想: 当你的测试脚本被设计成模块化(不同业务由不同线程组负责),并且模块间存在数据依赖时,就必须使用跨线程组传值。


二、如何实现跨线程组传值?

JMeter本身不鼓励线程组间通信(因为它们本应独立并发),但提供了__property函数和${__setProperty(...)}函数组合来实现这个功能,通常称为使用Properties(属性)

JMeter的属性(Properties)是全局的、跨线程组的,而变量(Variables)是线程本地的、属于同一个线程组的

实现原理:两步骤(“写”和“读”)

  1. 在线程组A(数据生产者)中:将变量提升为全局属性。
  2. 在线程组B(数据消费者)中:从全局属性中读取值,并转换为本地变量。

具体操作示例:传递登录Token

假设我们需要将setUp Thread Group中登录接口返回的token传递给另一个普通线程组。

第1步:在线程组A(生产者)中“写”入属性

  1. 在登录请求下添加一个正则表达式提取器JSON提取器,从响应中提取token值,保存到局部变量(如local_token)中。

  2. 在登录请求后,添加一个 BeanShell PostProcessorJSR223 PostProcessor(推荐后者,性能更好)。

    • 选择JSR223 PostProcessor
    • 语言选择(如groovy)。
    • 脚本框中,写入以下代码:
    // 将局部变量 ‘local_token’ 的值设置为一个全局属性,命名为 ‘GLOBAL_TOKEN'
    props.put("GLOBAL_TOKEN", vars.get("local_token"));
    
    // 可选:打印日志查看是否设置成功
    log.info(">>>>>>> 已将Token设置为全局属性: " + props.get("GLOBAL_TOKEN"));
    
    • 解释:
      • props:是JMeter的Properties对象,是全局的。
      • vars:是JMeter的Variables对象,是线程组内局部的。
      • vars.get("local_token"):获取本线程组内变量local_token的值。
      • props.put("GLOBAL_TOKEN", ...):将这个值以GLOBAL_TOKEN为名,存入全局属性池。

第2步:在线程组B(消费者)中“读”取属性

在线程组B的HTTP请求中,你不能直接使用${GLOBAL_TOKEN},因为它是局部变量。你需要使用JMeter函数来实时获取全局属性。

  1. 在线程组B的开始处,添加一个用户定义的变量(可选,为了清晰),或者直接在需要的地方使用函数。
  2. 在需要token的请求(如“查询用户信息”的请求)中,在请求头或参数里这样引用:
    • 在HTTP头管理器中,添加一个头,比如:
      • 名称: Authorization
      • 值: Bearer ${__property(GLOBAL_TOKEN)}
    • 解释:
      • __property函数是核心。${__property(GLOBAL_TOKEN)}的作用是:从全局属性池中查找名为GLOBAL_TOKEN的属性,并返回其值。

更优雅的写法:使用 __setProperty函数

在上面的第1步中,我们用了JSR223脚本来设置属性。其实,有一个更简单的方法,可以直接使用JMeter内置函数:

在线程组A的登录请求后,添加一个 BeanShell PostProcessor,里面只需要一行代码:

${__setProperty(GLOBAL_TOKEN, ${local_token},)}

或者,更推荐在 JSR223 PostProcessor 中使用:

// 使用函数调用方式,效果相同
org.apache.jmeter.util.JMeterUtils.setProperty("GLOBAL_TOKEN", vars.get("local_token"));

三、重要注意事项和技巧

  1. 执行顺序控制: 确保“写”属性的线程组(如setUp Thread Group)在“读”属性的线程组之前执行。你可以通过线程组的调度器或直接利用setUp Thread Group(它会最先执行)和tearDown Thread Group(它会最后执行)的特性来控制。
  2. 属性值的动态性: 属性是全局的,后设置的值会覆盖先设置的值。如果你的测试中,线程组A会运行多次并更新属性,那么线程组B读到的是最后一次设置的值
  3. 默认值: __property函数可以设置默认值,防止属性不存在时报错。语法:${__property(GLOBAL_TOKEN, TOKEN_NOT_FOUND)},如果GLOBAL_TOKEN不存在,函数会返回TOKEN_NOT_FOUND
  4. 调试: 使用 Debug SamplerView Results Tree 来检查:
    • 在线程组A中,检查local_token是否提取成功。
    • 在线程组B中,添加一个Debug Sampler,检查${__property(GLOBAL_TOKEN)}是否能正确获取到值。

总结

步骤 位置 操作 关键函数/对象
1. 写(设置) 数据生产线程组 在请求后添加JSR223 PostProcessor props.put(“GLOBAL_NAME”, vars.get(“local_var”))${__setProperty(...)}
2. 读(获取) 数据消费线程组 在请求的参数或头中直接使用函数 ${__property(GLOBAL_NAME)}

当你的性能测试场景需要模块化设计数据有关联时,跨线程组传值是必须掌握的技能。它通过JMeter的Properties(属性)机制,巧妙地打破了线程组之间的数据隔离。

融入CICD流程

什么是Ant和Allure

好的,我们来详细解释一下JMeter的Ant和Allue报告。它们代表了两种不同但可以协同工作的报告生成方式。

简单来说:

Ant: 是一个自动化构建和调度工具,用于自动执行JMeter测试脚本并生成JMeter默认的报告。

Allure: 是一个非常炫酷、交互性强的测试报告框架,用于展示更美观、更详细、更利于分析的测试结果。

它们的关系是:可以用Ant来定时自动运行JMeter测试,然后将结果数据传递给Allure,最终由Allure生成一份强大的HTML报告。

下面我们分别详细说明。


一、Apache Ant:自动化执行的“引擎”

1. 什么是Ant?

Apache Ant是一个用Java编写的构建工具,类似于Make。它使用XML文件(通常命名为 build.xml)来描述构建任务(如编译、打包、测试、部署)。

2. Ant在JMeter中的作用是什么?

在JMeter的语境下,Ant本身不直接生成性能测试报告。它的核心作用是:

自动化执行: 你可以编写一个Ant的 build.xml配置文件,在里面定义如何启动JMeter、运行哪个.jmx脚本、结果文件放在哪里等。然后,通过一行命令 ant就可以自动执行整个测试流程。

集成到CI/CD: 这是最关键的作用。你可以将Ant任务集成到Jenkins、GitLab CI等持续集成工具中,实现代码提交后自动进行性能测试。

生成JMeter默认的HTML报告: Ant可以配置一个特殊的任务,在JMeter测试完成后,调用JMeter的功能将原始的.jtl结果文件转换为一个格式更友好、带图表的HTML报告(这是JMeter自带的功能,Ant只是自动化了这个过程)。

3. 一个典型的Ant build.xml示例(简化版)

<project name="jmeter-tests" default="all">
    <!-- 定义属性,如JMeterhome -->
    <property name="jmeter.home" value="/path/to/your/apache-jmeter-5.6.2" />
    <property name="report.dir" value="./reports" />

    <!-- 清理历史结果 -->
    <target name="clean">
        <delete dir="${report.dir}" />
    </target>

    <!-- 创建报告目录 -->
    <target name="create">
        <mkdir dir="${report.dir}" />
    </target>

    <!-- 运行JMeter测试 -->
    <target name="test">
        <taskdef name="jmeter" classname="org.programmerplanet.ant.taskdefs.jmeter.JMeterTask" />
        <jmeter jmeterhome="${jmeter.home}" resultlog="${report.dir}/result.jtl">
            <testplans dir="." includes="MyTestPlan.jmx" />
        </jmeter>
    </target>

    <!-- 生成HTML报告 -->
    <target name="report">
        <xslt in="${report.dir}/result.jtl" out="${report.dir}/index.html" style="${jmeter.home}/extras/jmeter-results-detail-report_21.xsl" />
    </target>

    <!-- 总任务 -->
    <target name="all" depends="clean, create, test, report" />
</project>

运行它只需要在命令行输入:ant

Ant报告的特点:

生成的HTML报告是JMeter自带的,样式比较传统、简单。

主要包含:摘要、APDEX(用户满意度指数)、统计表格、响应时间随时间变化图等。

优点是生成速度快,是JMeter原生支持。


二、Allure:现代化、交互式的“仪表盘”

1. 什么是Allure?

Allure是一个轻量级、多语言的测试报告框架,最初是为单元测试、接口测试设计的,以极高的交互性和美观性而闻名。社区通过插件的形式让其支持JMeter。

2. Allure在JMeter中的作用是什么?

Allure的作用是展示。它可以将JMeter生成的原始结果文件(.jtl.csv转换成一个非常炫酷的Web报告。

它的报告强大在哪里?

  1. 美观的仪表盘: 有清晰的摘要信息,如总测试用例数、通过率、持续时间等。
  2. 丰富的图表: 提供多种图表展示测试结果,如趋势图、饼图、分布图等。
  3. 强大的交互性和钻取能力:
    • 你可以点击图表中的任何一个点(比如一个异常的峰值),直接下钻看到在那个时间点附近发生了哪些请求,它们的响应时间、状态码是什么。这对于定位问题至关重要!
    • 可以对请求进行分类筛选(如只看失败的请求、只看某个采样器的请求)。
  4. 支持附件: 可以将请求和响应的详细信息作为附件查看,方便调试。

3. 如何使用Allure生成JMeter报告?

  1. 安装Allure命令行工具到你的系统。
  2. 安装JMeter的Allure监听器插件。将 .jar文件放入JMeter的 lib/ext目录。
  3. 配置JMeter测试计划:
    • 在你的测试计划中,添加一个 Allure Backend Listener
    • 配置结果文件的存储路径。
  4. 运行JMeter测试。 监听器会将结果写入指定的目录(一种Allure支持的格式)。
  5. 生成Allure报告:
    • 在命令行中,进入结果目录,执行 allure serve /path/to/result/directory
    • 它会自动启动一个本地Web服务器并在浏览器中打开华丽的报告。

Allure报告的特点:

  • 优点: 极其美观、交互性无敌、利于深度分析问题、与现代化开发流程完美契合。
  • 缺点: 需要额外安装配置,报告生成过程比原生报告稍复杂。

三、总结与对比

特性 JMeter原生报告(通过Ant触发) Allure报告
核心角色 自动化执行引擎 + 基础报告生成器 高级报告可视化框架
报告样式 传统、静态的HTML页面 现代化、动态交互的Web应用
交互性 弱,主要是静态图表 极强,支持点击下钻、筛选、排序
集成难度 中等,需要编写Ant的build.xml 中等,需要安装Allure命令行和JMeter插件
主要优势 自动化调度,与CI/CD无缝集成,原生支持 卓越的分析和展示能力,便于定位问题和分享结果
典型工作流 ant-> 运行JMX -> 生成JTL -> XSLT转换 -> 原生HTML报告 jmeter -n -t ...-> Allure监听器写结果 -> allure serve-> 交互式报告

现代实践建议

在实际的CI/CD流水线中,最佳实践通常是将它们结合起来

  1. 使用Ant(或在Jenkins中直接使用JMeter命令) 在无人值守的服务器上自动执行JMeter性能测试。
  2. 配置测试计划使用 Allure Backend Listener,或者生成一个详细的XML格式的JTL文件。
  3. 测试完成后,使用 Allure命令行工具 去解析结果文件,生成最终的交互式报告
  4. 将Allure报告的HTML文件存档,或发布到某个Web服务器,供团队查看。

这样,你既利用了Ant的自动化能力,又享受了Allure报告强大的分析功能,实现了“自动化执行 + 精美报告”的最佳效果。

posted @ 2025-11-30 19:23  GDms  阅读(0)  评论(0)    收藏  举报