更清晰、更可维护 的 JMeter→DSL 结构化方案。
所有接口都直接写在同一个 transaction 里
transaction 一多,可读性会迅速下降
想保持 transaction 列表清晰,但接口定义放到外部
下面给你 最佳实践级 的组织方式。
目标结构(推荐)
把 DSL 拆成 3 层:
transaction 列表:非常简洁,只描述要执行的逻辑流程
➡️ 文件:Scenario.java
DslTestPlan testPlan = testPlan(
threadGroup(1, 10,
transaction("LoginFlow", FlowDefinitions.loginFlow()),
transaction("OrderFlow", FlowDefinitions.orderFlow()),
transaction("SearchFlow", FlowDefinitions.searchFlow())
)
);
这里你只需要维护「场景顺序」,完全看不到繁琐的接口细节。
② 每个 transaction 的接口组合:独立文件维护
➡️ 文件:FlowDefinitions.java
public class FlowDefinitions {
public static DslController loginFlow() {
return controller(
ApiDefinitions.login(),
ApiDefinitions.getUserInfo()
);
}
public static DslController orderFlow() {
return controller(
ApiDefinitions.createOrder(),
ApiDefinitions.queryOrder(),
ApiDefinitions.payOrder()
);
}
public static DslController searchFlow() {
return controller(
ApiDefinitions.search(),
ApiDefinitions.searchDetails()
);
}
}
每个事务就是一个方法,接口属于组合逻辑,不再塞在主文件里。
③ 所有 API(HTTP Samplers)统一放在一个 API 定义文件
➡️ 文件:ApiDefinitions.java
public class ApiDefinitions {
public static DslHttpSampler login() {
return httpSampler("POST", "/auth/login")
.param("username", "test")
.param("password", "123");
}
public static DslHttpSampler getUserInfo() {
return httpSampler("GET", "/user/info");
}
public static DslHttpSampler createOrder() {
return httpSampler("POST", "/order");
}
public static DslHttpSampler queryOrder() {
return httpSampler("GET", "/order/query");
}
public static DslHttpSampler payOrder() {
return httpSampler("POST", "/order/pay");
}
public static DslHttpSampler search() {
return httpSampler("GET", "/search");
}
public static DslHttpSampler searchDetails() {
return httpSampler("GET", "/search/details");
}
}
你可以把所有 API 干干净净地放在一个地方,搜索、维护都非常容易。
🚀 最终效果
Scenario(主文件)极度简洁,只看流程,不看接口细节:
LoginFlow
OrderFlow
SearchFlow
各流程自己维护接口组合
每个 API 的定义集中归档
🔥 优点
✔ 非常清晰、可维护
transaction 文件只有流程,不会堆满 sampler。
✔ 接口定义复用度极高
相同接口可同时被多个 transactions 复用。
✔ 结构跟真实系统更像
「流程」与「API」分离 → 适合大规模测试。
✔ 扩展新场景非常快
只要新增:
新 flow()
或直接组合已有 API
scenario 文件几乎不用改
已完成!🎉
我已经将你要求的 三个独立 Java DSL 文件全部重构并生成,可直接下载使用:
✅ 下载文件
Scenario_MustHave3.java
FlowDefinitions.java
ApiDefinitions.java
🔧 重构说明
根据你选择的模式:
Scenario 层只保留 transaction 列表 + 全局配置
全局 vars()
threadGroup
httpDefaults()
httpHeaders()
以及 transaction 部分:
transaction("ACBN-", FlowDefinitions.acbnFlow())
FlowDefinitions 即负责把 transaction = sampler 组装
示例:
public static DslController acbnFlow() {
return controller(
ApiDefinitions.acbnTokenGet()
);
}
ApiDefinitions 全部存放每个 HTTP sampler 的细节
包括:
method
URL
extractor
responseAssertion
debugPostProcessor 等
完全保留你的原始逻辑。
如需继续增强 DSL 结构我可以协助:
例如:
🔹 自动扫描 API 并注册
🔹 给每个 transaction 自动加日志
🔹 多线程组 DSL 结构化
🔹 自动生成 flow 依赖图 / 时序图
🔹 把 JSON Extractor 拆成独立复用方法
有!虽然 JMeter GUI 的「Simple Data Writer / Result Collector」有日志过滤(成功/失败),
但是在 JMeter Java DSL 里,已经把这些功能合并到 jtlWriter / resultsTree / resultsSummary 内部了,不再用 "日志控制器(Log Controller)" 这种组件。
DSL对应说明
Simple Data Writer → Save Only Failures
jtlWriter().logFailed()
只保存失败请求
Save Only Success
jtlWriter().logOnlySuccess()
保存成功请求
Save All Requests
jtlWriter()(默认)
保存全部
控制输出内容字段
DSL 自动管理,不需要手动配置
DSL 简化了字段选择
✅ DSL 中真实可用的控制器(等价于 JMeter 的日志控制器)
- 只记录失败(最常用)
jtlWriter("target/jtls", reportName + ".jtl")
.logFailed();
- 只记录成功
jtlWriter("target/jtls", reportName + ".jtl")
.logOnlySuccess();
- 同时控制 Summary / Tree Listener
也可以:
resultsTree()
.logFailed();
resultsSummary()
.logFailed();
这两个完全等价于 JMeter GUI 的:
View Results Tree(只看失败)
Summary Report(只看失败)
🔥 DSL 中没有一个叫 "Log Controller(日志控制器)" 的组件
因为 DSL 做了"组件融合",将 JMeter 的 listener + log 控制统一整合到:
✔ jtlWriter()
✔ resultsTree()
✔ resultsSummary()
这三个组件就是 DSL 中的 "日志控制器"。
🔧 如果你想做到:
✔ 保存失败日志 +
✔ 自带失败日志打印 +
✔ 自动生成失败报告
我可以帮你写一个 全局失败采集器(FailCollector),像这样:
FailCollector.enable("logs/failed");
然后自动:
收集失败 URL
收集 Request Body
收集响应内容
保存成 JSON 或文本
自动按日期分类
package org.must_have;
import org.apache.http.entity.ContentType;
import org.junit.Test;
import org.loadtest.TestRunner;
import org.loadtest.TestRunnerLoadTest;
import us.abstracta.jmeter.javadsl.core.listeners.JtlWriter;
import us.abstracta.jmeter.javadsl.core.stats.StatsSummary;
import java.io.IOException;
import java.time.Duration;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static us.abstracta.jmeter.javadsl.JmeterDsl.*;
public class MustHave3 extends TestRunnerLoadTest {
@Test
public void test() throws IOException {
stats = testPlan()
.sequentialThreadGroups()
.children(
vars()
.set("customer_id", "1395000627532406784")
.set("host", "api-sta2.sta-wlab.net"),
threadGroup("Must Have API")
.rampTo(1, Duration.ofSeconds(1))
.holdIterating(1)
.upTo(Duration.ofSeconds(1))
.children(
httpDefaults()
.url("https://api-sta2.sta-wlab.net"),
httpHeaders()
.header("accept", "*/*")
.header("Accept-Language", "en-US")
.contentType(ContentType.create("application/json", "UTF-8")),
transaction("TAT-T3387", TransactionControllerDefinitions.AddMoney())
// ,resultsTreeVisualizer()
)
.children(
jsr223PostProcessor(c ->System.out.println(c.prevMap())),
jsr223PostProcessor(c ->System.out.println(c.prevRequest())),
jsr223PostProcessor(c ->System.out.println(c.prevResponse()))
),
htmlReporter("reports",getTimeString()),
jtlWriter("target/jtls",reportName+".jtl"),
// jtlWriter("target/jtls",reportName+".xml").withAllFields().withSuccess(true), //all log
jtlWriter("target/jtls",reportName+"_success.xml").withAllFields().logOnly(JtlWriter.SampleStatus.SUCCESS),//SUCCESS log
jtlWriter("target/jtls",reportName+"_error.xml").withAllFields().logOnly(JtlWriter.SampleStatus.ERROR)//Error log
).run();
// assertThat(stats.overall().errorsCount()).isEqualTo(0);
}
public static void main(String[] args) {
// SummaryGeneratingListener summaryListener = new SummaryGeneratingListener();
// LauncherFactory.create()
// .execute(LauncherDiscoveryRequestBuilder.request()
// .selectors(DiscoverySelectors.selectClass(MustHave3.class))
// .build(),
// summaryListener);
// TestExecutionSummary summary = summaryListener.getSummary();
// summary.printFailuresTo(new PrintWriter(System.err));
// System.exit(summary.getTotalFailureCount() > 0 ? 1 : 0);
}
}
package org.must_have;
import org.apache.jmeter.protocol.http.util.HTTPConstants;
import us.abstracta.jmeter.javadsl.core.controllers.DslController;
import us.abstracta.jmeter.javadsl.core.controllers.DslTransactionController;
import us.abstracta.jmeter.javadsl.core.postprocessors.DslJsonExtractor;
import static us.abstracta.jmeter.javadsl.JmeterDsl.*;
import static us.abstracta.jmeter.javadsl.JmeterDsl.debugPostProcessor;
import static us.abstracta.jmeter.javadsl.JmeterDsl.httpSampler;
import static us.abstracta.jmeter.javadsl.JmeterDsl.jsonExtractor;
import static us.abstracta.jmeter.javadsl.JmeterDsl.regexExtractor;
public class TransactionControllerDefinitions {
public static DslTransactionController AddMoney() {
return transaction("Add Money",
ApiDefinitions.getToken(),
ApiDefinitions.getBankMandateId(),
ApiDefinitions.postAddMoney(),
ApiDefinitions.getrecentTransactions()
);
}
public static DslTransactionController LoanApplications() {
return transaction("Loan Applications",
ClpApiDefinitions.getToken(),
ClpApiDefinitions.postLoanApplications(),
ClpApiDefinitions.putLaonApplication(),
ClpApiDefinitions.postLoanId()
);
}
}
package org.must_have;
//import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import org.apache.jmeter.protocol.http.util.HTTPConstants;
import us.abstracta.jmeter.javadsl.core.postprocessors.DslJsonExtractor;
import us.abstracta.jmeter.javadsl.http.DslHttpSampler;
import static org.assertj.core.api.Assertions.assertThat;
import static us.abstracta.jmeter.javadsl.JmeterDsl.*;
import static us.abstracta.jmeter.javadsl.wrapper.WrapperJmeterDsl.testElement;
public class ApiDefinitions {
public static DslHttpSampler getToken() {
// return httpSampler("get token", "https://api-sta2.sta-wlab.net/onboarding-pro/jwt/${customer_id}")
return httpSampler("get token", "/onboarding-pro/jwt/${customer_id}")
.method(HTTPConstants.GET)
.children(
responseAssertion().containsSubstrings("Bearer"),
regexExtractor("token", "(.*)"),
debugPostProcessor()
);
}
public static DslHttpSampler getBankMandateId() {
return httpSampler("/payment-mob/v1/payments/edda/banks/me", "https://api-sta2.sta-wlab.net/payment-mob/v1/payments/edda/banks/me")
.method(HTTPConstants.GET)
.header("Idempotency-Key", "${__UUID()}")
.header("Authorization", "${token}")
.children(
jsonExtractor("mandateId", "$.data[0].mandateId")
.queryLanguage(DslJsonExtractor.JsonQueryLanguage.JSON_PATH)
.defaultValue("not_found")
);
}
public static DslHttpSampler postAddMoney() {
return httpSampler("/payment-mob/v1/payments/direct-debit", "https://api-sta2.sta-wlab.net/payment-mob/v1/payments/direct-debit")
.method(HTTPConstants.POST)
.header("Idempotency-Key", "${__UUID()}")
.header("Authorization", "${token}")
.body("{\r\n"
-
" \"mandateId\": \"${mandateId}\",\r\n"
-
" \"ruleId\": null,\r\n"
-
" \"settlementAmt\": 100\r\n"
-
"}")
.children(
);
}
public static DslHttpSampler getrecentTransactions() {
return httpSampler("/bank-account-mob/v4/bank-accounts/home/recent-transactions", "https://${host}/bank-account-mob/v4/bank-accounts/home/recent-transactions")
.method(HTTPConstants.GET)
.header("Idempotency-Key", "${__UUID()}")
.header("Authorization", "${token}")
.children(
jsonExtractor("amount", "$.data.transactions[0].amount")
.queryLanguage(DslJsonExtractor.JsonQueryLanguage.JSON_PATH)
.defaultValue("not_found")
);
}
}