JMeter Beanshell 使用指南

在本文中,我们探讨了如何有效地使用 JMeter 中的 BeanShell 向测试计划添加自定义脚本。我们介绍了预处理器、采样器、后处理器和监听器等重要组件,展示了如何操作请求数据、处理响应和记录指标。

在本快速教程中,我们将创建一个使用JMeter中提供的大多数BeanShell功能的测试用例。最后,我们将学习如何使用必要的工具通过 BeanShell 脚本进行任何测试。

设置JMeter和BeanShell
首先,我们需要下载 JMeter。要运行它,我们需要将下载的文件解压到任意位置,然后运行可执行文件(对于基于 Unix 的系统,运行 jmeter.sh ;对于 Windows,运行 jmeter.bat)。

在撰写本文时,最新的稳定版本是 5.6.3,它已经捆绑了 BeanShell 2.0b6 二进制文件。版本 3 目前正在开发中,将支持较新的 Java 功能。在此之前,我们只能使用 Java 4 语法,尽管 JMeter 可以在最新的 JVM 上运行。我们稍后会看到这对我们的脚本有何影响。

最重要的是,虽然 BeanShell 是一种功能丰富的脚本语言,但它主要用于在 JMeter 中实现测试步骤。我们将在我们的场景中实际看到这一点:向 API 发出 POST 请求,同时捕获统计数据,例如发送的总字节数和耗时。

启动 JMeter 后,我们需要的唯一非 BeanShell 元素是线程组。我们通过右键单击“测试计划”,然后选择“添加”,然后选择“线程(用户)”,然后选择“线程组”来创建一个线程组。我们只需要指定所需的线程数和循环数

完成后,我们就可以为测试创建基于 BeanShell 的元素了。

预处理器
我们将从预处理器开始,为我们的 POST 请求配置值。我们通过右键单击线程组,然后单击“添加”,然后单击“预处理器”,然后单击“BeanShell 预处理器”来执行此操作。在其内容中,让我们编写一个脚本来为请求生成随机值:

random = new Random();
key = "k"+random.nextInt();
value = random.nextInt();

要声明变量,我们不需要明确包含它们的类型,我们可以使用 JVM 或 JMeter 的lib文件夹中可用的任何类型。

让我们使用vars对象(脚本之间共享的特殊变量)来保存这些值以供以后使用,以及我们的 API 地址:

vars.put("base-api", "http://localhost:8080/api");
vars.put(
"key", key);
vars.putObject(
"value", value);

我们使用 putObject()作为值,因为put()仅接受字符串值。我们的最终变量定义当前线程应打印摘要之前需要进行多少次测试迭代。我们稍后会用到它:

vars.putObject("summary-iterations", 5)

采样器
我们的采样器检索先前存储的值,并使用与 JMeter 捆绑在一起的Apache HTTP框架将它们发送到 API。最重要的是,我们需要为脚本中直接提及的任何不在默认导入列表中的类添加导入语句。

为了创建请求主体,我们将使用String.format()的较旧的new Object[]{…}语法,因为varargs是 Java 5 的功能:

url = vars.get("base-api");
json = String.format(
 
"{\"key\": \"%s\", \"value\": %s}"
  new Object[]{ vars.get(
"key"), vars.get("value") }
);

现在,让我们执行请求:

client = HttpClients.createDefault();
body = new StringEntity(json, ContentType.APPLICATION_JSON);
post = new HttpPost(url);
post.setEntity(body);
response = client.execute(post);

JMeter 包含ResponseCode和ResponseMessage变量,我们将在后续阶段检索它们:

ResponseCode = response.getStatusLine().getStatusCode();
ResponseMessage = EntityUtils.toString(response.getEntity());


最后,由于我们不能使用 try-with-resources 块,我们关闭资源并为脚本选择一个返回值:

response.close();
client.close();
return json;


返回值可以是任意类型。稍后我们将返回请求主体,以便计算请求大小。

后处理器
后处理器在采样器之后立即执行。我们将使用它来收集和汇总所需的信息。让我们创建一个函数来增加变量并保存结果:

incrementVar(name, increment) {
    value = (Long) vars.getObject(name);
    if (value == null
      value = 0l;
    value += increment;
    vars.putObject(name, value);
    log.info("{}: {}", name, value);
}

这里,我们还有一个记录器,无需额外配置。它将记录到 JMeter 控制台。请注意,可见性修饰符、返回类型和参数类型不是必需的;它们是从上下文推断出来的。

我们将增加每次线程迭代的耗时和发送/接收的字节数。prev变量允许我们从上一个脚本中获取该信息:

incrementVar("elapsed-time-total", prev.getTime());
incrementVar(
"bytes-received-total", prev.getResponseMessage().getBytes().length);
incrementVar(
"bytes-sent-total", prev.getBytesAsLong());

监听器
监听器在 PostProcessor 之后运行。我们将使用一个监听器将报告写入文件系统。首先,让我们编写一个辅助函数并设置一些变量:

println(writer, message, arg1) {
    writer.println(String.format(message, new Object[] {arg1}));
}
thread = ctx.getThread();
threadGroup = ctx.getThreadGroup();
request = prev.getResponseDataAsString();
response = prev.getResponseMessage();

ctx变量提供来自线程组和当前线程的信息。接下来,让我们构建一个FileWriter来在主目录中写入报告:

fw = new FileWriter(new File(System.getProperty("user.home"), "jmeter-report.txt"), true);
writer = new PrintWriter(new BufferedWriter(fw));
println(writer,
"* iteration: %s", vars.getIteration());
println(writer,
"* elapsed time: %s ms.", prev.getTime());
println(writer,
"* request: %s", request);
println(writer,
"= %s bytes", prev.getBytesAsLong());
println(writer,
"* response body: %s", response);
println(writer,
"= %s bytes", response.getBytes().length);

由于此操作针对每个线程迭代运行,因此我们调用vars.getIteration()来跟踪迭代次数。最后,如果当前迭代是summary-iterations的倍数 ,我们将打印摘要:

if (vars.getIteration() % vars.getObject("summary-iterations") == 0) {
    println(writer,
"<strong>summary for %s", thread.getThreadName());</strong>
    println(writer,
"* total bytes sent: %s bytes", vars.get("bytes-sent-total"));
    println(writer,
"* total bytes received: %s bytes", vars.get("bytes-received-total"));
    println(writer,
"* total elapsed time: %s ms.", vars.get("elapsed-time-total"));
}

最后,让我们关闭writer:

writer.close();