接口测试: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 keepaliveUse 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 请求。
核心步骤:
- 分析加密需求(什算法、什么参数)
- 在 JSR223 中实现加密逻辑
- 将加密结果保存为变量
- 在 HTTP 请求中使用这些变量
- 验证解密的响应数据
掌握这些技巧,你就能测试各种复杂的加密接口了!
跨线程组
线程组的启动方式
好的,这是一个关于JMeter使用的基础且重要的问题。
直接回答:是的,默认情况下,当你点击工具栏的“启动”(Start)按钮时,测试计划中所有的线程组都会按从上到下的顺序依次执行。
详细解释与注意事项
1. 默认行为:顺序执行
JMeter会按照你在测试计划视图中看到的线程组从上到下的顺序来执行。
- 示例: 如果你的测试计划包含:
线程组 A- 模拟用户登录线程组 B- 模拟用户浏览商品线程组 C- 模拟用户下单
- 执行顺序: JMeter会先启动并完成
线程组 A的所有线程(包括循环),然后再启动线程组 B,最后是线程组 C。
2. 如何控制线程组的执行?
如果你不希望所有线程组都运行,或者希望它们并行执行,有以下几种方法:
| 方法 | 说明 | 适用场景 |
|---|---|---|
| 绿色启动按钮 | 运行所有启用的线程组。 这是最常用的方式。 | 常规的、顺序的负载测试。 |
| 禁用线程组 | 在测试计划中,右键点击某个线程组 -> 禁用(Disable)。被禁用的线程组名称会变灰,启动测试时将被完全跳过。 | 你只想测试其中一两个场景,暂时不想运行其他线程组。 |
| 单独运行线程组 | 右键点击某个线程组 -> 启动(Start)。此时只有这个被选中的线程组会运行,其他线程组不会执行。 | 调试某个特定的业务场景,非常方便。 |
3. 一个重要的高级选项:独立运行每个线程组
在线程组的配置中,有一个关键的选项会影响其行为:
- 位置: 选中一个线程组,在右侧的配置面板中,勾选
Run Thread Groups consecutively (i.e. one at a time)(依次运行线程组(即一次一个))。 - 这个选项的作用:
- 如果勾选(默认): 如上所述,线程组按顺序执行,一个完成后才启动下一个。
- 如果不勾选: 所有线程组会同时启动,并行执行。 这对于模拟不同用户群体在同一时间进行不同操作(如一部分人登录,一部分人查询)的场景非常有用。
最佳实践建议
- 清晰命名: 为你的线程组起一个清晰的名字(如“01-用户登录”、“02-商品浏览”),便于管理和识别。
- 调试时禁用/单独运行: 在编写和调试测试脚本时,充分利用“禁用”和“右键启动”功能,可以大大提高效率。
- 理解执行顺序: 在设计混合场景测试时,一定要清楚线程组的执行顺序(顺序还是并行),以确保测试场景符合真实的业务逻辑。
总结
| 你的操作 | 结果 |
|---|---|
| 点击顶部的绿色启动按钮 | 所有已启用的线程组会按顺序(或并行,取决于上述选项)执行。 |
| 禁用一个线程组,然后点击绿色启动按钮 | 只有未禁用的线程组会执行。 |
| 右键点击一个线程组,选择 启动 | 只有这个被选中的线程组会执行。 |
所以,请根据你的测试目标,灵活运用这些功能来控制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 Group和tearDown Thread Group结合使用。
- 典型例子:登录Token传递
- 线程组A (
setUp Thread Group): 只执行一次,用于全局初始化,比如获取一个一次性的、有有效期的认证Token。 - 线程组B (普通线程组): 模拟主要业务操作(如查询、下单)。这些操作都需要使用线程组A中获取的Token来构造请求头。
- 需求: 线程组B需要能拿到线程组A中生成的Token。
- 线程组A (
- 典型例子:工作流数据传递
- 线程组A (创建数据): 模拟用户创建一条订单,生成一个
orderId。 - 线程组B (处理数据): 模拟另一个流程(如支付、查询订单详情),需要使用线程组A创建的
orderId。 - 需求: 线程组B需要能拿到线程组A中生成的
orderId。
- 线程组A (创建数据): 模拟用户创建一条订单,生成一个
场景二:并发共享型(较少见)
- 典型例子:共享配置
- 一个线程组从一个配置接口获取一些全局参数(如服务器时间、商品列表),然后多个并发线程组同时使用这些参数。虽然这种情况也可以用
setUp Thread Group实现,但有时业务逻辑要求这些配置需要动态获取。
- 一个线程组从一个配置接口获取一些全局参数(如服务器时间、商品列表),然后多个并发线程组同时使用这些参数。虽然这种情况也可以用
核心思想: 当你的测试脚本被设计成模块化(不同业务由不同线程组负责),并且模块间存在数据依赖时,就必须使用跨线程组传值。
二、如何实现跨线程组传值?
JMeter本身不鼓励线程组间通信(因为它们本应独立并发),但提供了__property函数和${__setProperty(...)}函数组合来实现这个功能,通常称为使用Properties(属性)。
JMeter的属性(Properties)是全局的、跨线程组的,而变量(Variables)是线程本地的、属于同一个线程组的。
实现原理:两步骤(“写”和“读”)
- 在线程组A(数据生产者)中:将变量提升为全局属性。
- 在线程组B(数据消费者)中:从全局属性中读取值,并转换为本地变量。
具体操作示例:传递登录Token
假设我们需要将setUp Thread Group中登录接口返回的token传递给另一个普通线程组。
第1步:在线程组A(生产者)中“写”入属性
-
在登录请求下添加一个正则表达式提取器或JSON提取器,从响应中提取
token值,保存到局部变量(如local_token)中。 -
在登录请求后,添加一个 BeanShell PostProcessor 或 JSR223 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函数来实时获取全局属性。
- 在线程组B的开始处,添加一个用户定义的变量(可选,为了清晰),或者直接在需要的地方使用函数。
- 在需要
token的请求(如“查询用户信息”的请求)中,在请求头或参数里这样引用:- 在HTTP头管理器中,添加一个头,比如:
- 名称:
Authorization - 值:
Bearer ${__property(GLOBAL_TOKEN)}
- 名称:
- 解释:
__property函数是核心。${__property(GLOBAL_TOKEN)}的作用是:从全局属性池中查找名为GLOBAL_TOKEN的属性,并返回其值。
- 在HTTP头管理器中,添加一个头,比如:
更优雅的写法:使用 __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"));
三、重要注意事项和技巧
- 执行顺序控制: 确保“写”属性的线程组(如
setUp Thread Group)在“读”属性的线程组之前执行。你可以通过线程组的调度器或直接利用setUp Thread Group(它会最先执行)和tearDown Thread Group(它会最后执行)的特性来控制。 - 属性值的动态性: 属性是全局的,后设置的值会覆盖先设置的值。如果你的测试中,线程组A会运行多次并更新属性,那么线程组B读到的是最后一次设置的值。
- 默认值:
__property函数可以设置默认值,防止属性不存在时报错。语法:${__property(GLOBAL_TOKEN, TOKEN_NOT_FOUND)},如果GLOBAL_TOKEN不存在,函数会返回TOKEN_NOT_FOUND。 - 调试: 使用 Debug Sampler 和 View Results Tree 来检查:
- 在线程组A中,检查
local_token是否提取成功。 - 在线程组B中,添加一个Debug Sampler,检查
${__property(GLOBAL_TOKEN)}是否能正确获取到值。
- 在线程组A中,检查
总结
| 步骤 | 位置 | 操作 | 关键函数/对象 |
|---|---|---|---|
| 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报告。
它的报告强大在哪里?
- 美观的仪表盘: 有清晰的摘要信息,如总测试用例数、通过率、持续时间等。
- 丰富的图表: 提供多种图表展示测试结果,如趋势图、饼图、分布图等。
- 强大的交互性和钻取能力:
- 你可以点击图表中的任何一个点(比如一个异常的峰值),直接下钻看到在那个时间点附近发生了哪些请求,它们的响应时间、状态码是什么。这对于定位问题至关重要!
- 可以对请求进行分类筛选(如只看失败的请求、只看某个采样器的请求)。
- 支持附件: 可以将请求和响应的详细信息作为附件查看,方便调试。
3. 如何使用Allure生成JMeter报告?
- 安装Allure命令行工具到你的系统。
- 安装JMeter的Allure监听器插件。将
.jar文件放入JMeter的lib/ext目录。 - 配置JMeter测试计划:
- 在你的测试计划中,添加一个 Allure Backend Listener。
- 配置结果文件的存储路径。
- 运行JMeter测试。 监听器会将结果写入指定的目录(一种Allure支持的格式)。
- 生成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流水线中,最佳实践通常是将它们结合起来:
- 使用Ant(或在Jenkins中直接使用JMeter命令) 在无人值守的服务器上自动执行JMeter性能测试。
- 配置测试计划使用 Allure Backend Listener,或者生成一个详细的XML格式的JTL文件。
- 测试完成后,使用 Allure命令行工具 去解析结果文件,生成最终的交互式报告。
- 将Allure报告的HTML文件存档,或发布到某个Web服务器,供团队查看。
这样,你既利用了Ant的自动化能力,又享受了Allure报告强大的分析功能,实现了“自动化执行 + 精美报告”的最佳效果。

浙公网安备 33010602011771号