Jmeter脚本原理与实例

文章目录

简介

Jmeter是一个非常强大的测试工具,功能非常全,也非常灵活,灵活的地方在于几乎所有的组件都可以通过脚本自定义。

本文就主要介绍一下Jmeter的脚本语言的原理,方便大家在使用Jmeter的时候,可以更好的理解和编写脚本。

Jmeter支持很多语言,但是官方推荐的还是Groovy,所有我们会以Groovy为例。

原理

其实,Jmeter是使用Java实现的,它执行脚本本质就是通过ScriptEngineManager获取ScriptEngine来执行脚本的。

具体的源码可以看:

org.apache.jmeter.JMeter

org.apache.jmeter.functions.JavaScript

简单示例

一个非常简单的例子:

java 复制代码
 @Test
public void base() throws ScriptException {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("groovy");
    engine.eval("println 'Hello from Groovy using ScriptEngine!'");
    engine.eval("def name = 'World'; println 'Hello, ' + name + '!'");
}

上面就是获取了groovy的ScriptEngine,然后通过eval执行了groovy代码,是不是非常简单。

当然,需要引入对应版本的groovy依赖:

xml 复制代码
<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>3.0.25</version>
    <type>pom</type>
</dependency>

在Jmeter中已经帮我们引入好了,不需要我们操心。

注入实例变量

在Jmeter中脚本中,我们经常会使用到一些Jmeter的实例变量,比如:

  1. log
  2. ctx
  3. vars
  4. props
  5. threadName
  6. sampler
  7. sampleResult

这些其实都是Jmeter在执行脚本之前,已经帮我们注入到脚本中的,我们只需要在脚本中直接使用。

这就是Groovy脚本的强大之处,他完全兼容Java,所以我们可以直接使用这些变量示例。

java 复制代码
@Test
public void evalWithParam() throws ScriptException {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("groovy");
    Bindings bindings = engine.createBindings();

    User user = new User();
    bindings.put("arg", "hello word");
    bindings.put("user", user);

    String script = """
            println arg
            println user.hi("world")
            """;
    ScriptContext newContext = new SimpleScriptContext();
    newContext.setBindings(bindings, ScriptContext.ENGINE_SCOPE);

    Object result = engine.eval(script, newContext);
    if(result != null) {
        String resultStr = result.toString();
        System.out.println(resultStr);
    }
}

@Data
private static class User {

    private Long id;

    private String name;

    private Date birthday;

    public String hi(String name){
        return "hi " + name + " !";
    }
}

操作文件

在脚本中,最常用的操作就是解析json、操作文件。

Groovy也支持非常好,可以使用JsonSlurper解析json,使用JsonOutput将对象转换为json字符串。

java 复制代码
@Test
public void dealJson() throws ScriptException {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("groovy");
    Bindings bindings = engine.createBindings();

    User user = new User();
    user.setId(88L);
    user.setName("allen");
    user.setBirthday(new Date());
    bindings.put("arg", "hello word");
    bindings.put("user", user);

    String script = """
            import groovy.json.JsonSlurper
            import groovy.json.JsonOutput

            def jsonString = '{"name":"John", "age":30}'
            def jsonSlurper = new JsonSlurper()
            def jsonObject = jsonSlurper.parseText(jsonString)

            println jsonObject.name
            println jsonObject.age

            def person = [name: 'John', age: 30]
            def resultJsonString = JsonOutput.toJson(person)

            println resultJsonString

            resultJsonString = JsonOutput.toJson(user)
            println(resultJsonString)

            def writeFile = new File("F:\\\\tmp.json");
            writeFile.withWriter {
                writer->
                    writer.write(resultJsonString)
            }

            def readFile = new File("F:\\\\tmp.json");
            println "readFile.text: " + readFile.getText()
            return "success"
            """;
    ScriptContext newContext = new SimpleScriptContext();
    newContext.setBindings(bindings, ScriptContext.ENGINE_SCOPE);

    Object result = engine.eval(script, newContext);
    if(result != null) {
        String resultStr = result.toString();
        System.out.println(resultStr);
    }
}

JavaScript的问题

使用Jmeter不推荐使用Groovy之外的脚本语言,因为支持不好,效率不高。

对于JavaScript,Jmeter默认使用的是nashorn引擎。

但是高版本的JDK15+已经移除了nashorn引擎。

怎么办呢?

对于Jmeter,降低JDK版本,使用JDK15一下的版本。

自己测试可以引入:

xml 复制代码
<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js-scriptengine</artifactId>
    <version>23.0.0</version>
</dependency>
<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js</artifactId>
    <version>23.0.0</version>
</dependency>
java 复制代码
@Test
public void javaScript() throws ScriptException {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptContext newContext = new SimpleScriptContext();
//        ScriptEngine engine = manager.getEngineByName("nashorn");
//        ScriptEngine engine = manager.getEngineByName("graal.js");
    ScriptEngine engine = manager.getEngineByName("js");
    Bindings bindings = engine.createBindings();

    String script = """
            console.log(arg);
            let upperCaseString = arg.toUpperCase();
            console.log(upperCaseString);
            console.log(user);
            """;

    User user = new User();
    user.setId(88L);
    user.setName("allen");
    user.setBirthday(new Date());
    bindings.put("arg", "hello word");
    bindings.put("user", user);
    newContext.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
    Object result = engine.eval(script, newContext);
    if(result != null) {
        String resultStr = result.toString();
        System.out.println("调用结果:" + resultStr);
    }
}

非常不友好,不能直接调用对象方法,所以尽量选Groovy语言吧。

会Java的朋友的福音

有朋友可能会说我不想学groovy,怎么办呢?

对于会java的朋友,可以直接把groovy脚本当做java代码来写。

groovy会默认导入下面的包:

java 复制代码
java.io.*
java.lang.*
java.math.BigDecimal
java.math.BigInteger
java.net.*
java.util.*
groovy.lang.*
groovy.util.*

Jmeter的lib包中已经引入了httpclient、jackson、json-path、commons-lang3、jsoup、xstream等包,这些库都可以直接用。

例如,我们可以使用下面的代码来读取文件内容:

java 复制代码
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

Path path = Paths.get("F:\\tmp.json");
String content = Files.readString(path);
log.info(content);

Groovy默认导入的是java.io,所以使用nio的包,我们手动导入一下就可以。

所以,完全可以在IDE中写完Java代码,直接拷贝过去就可以。

当然,还是得注意依赖问题。

需要啥包,可以直接下载了忘Jmeter安装目录下的lib目录下扔就可以。

Grab

当然也可以使用Grab,它可以自动下载依赖包,会放在用户目录下的.groovy/grapes目录下。

可以通过System.setProperty("grape.root", "xxxx/.groovy/grapes")设置

Grab虽然方便,它及不兼容Gradle也不兼容Maven,而是使用ivy-xxx.jar,例如ivy-2.5.3.jar,有它直接的格式,有点问题就很难处理。

所以,对于Jmeter最方便的还是直接拷贝jar包到安装目录下的lib目录,然后重启Jmeter,就可以使用了。

groovy 复制代码
// @GrabResolver(name='aliyun', root='https://maven.aliyun.com/repository/public')
// @GrabConfig(systemProperties=['http.proxyHost=198.185.120.100', 'http.proxyPort=7000','https.proxyHost=198.185.120.100', 'https.proxyPort=7000','systemProp.https.proxyUser=xxx','systemProp.https.proxyPassword=pppp','systemProp.http.proxyUser=xxx','systemProp.http.proxyPassword=ppp'])
@Grab('com.alibaba.fastjson2:fastjson2:2.0.59')
import com.alibaba.fastjson2.JSON;

class User {
    private Integer age;
    private String name;

    Integer getAge() {
        return age
    }

    void setAge(Integer age) {
        this.age = age
    }

    String getName() {
        return name
    }

    void setName(String name) {
        this.name = name
    }
}

User user = new User();
user.setAge(18);
user.setName("tim");
String jsonString = JSON.toJSONString(user);
log.info("jsonString:{}", jsonString);

注意:Grab依赖apache的ivy,必须先有这个包才能自动下载

Jmeter注入变量

前面我们已经介绍了,Jmeter会在执行脚本之前先注入一些上下文信息,方便我们在脚本访问和引用。

但实际上不同的生命周期,我们能获取到的对象是不一样的,最典型的就是在后置处理器中通过SampleResult不能获取到结果。

是因为在后置处理器中,采样器的结果,已经算是前一个采样器的结果了,所以得通过ctx.getPreviousResult()或者prev获取。

  1. log:org.slf4j.Logger,记录日志的,实际使用的实现是log4j,配置文件为:安装目录/bin/log4j2.xml
  2. ctx:org.apache.jmeter.threads.JMeterContext,可以获取采样器、前一个采样器、采用结果、变量、线程组等信息
  3. vars:org.apache.jmeter.threads.JMeterVariables,最常用,最重要的,我们的变量设置获取就靠它
  4. props:java.util.Properties,Jmeter的一些属性信息,例如数据库驱动等
  5. sampler:org.apache.jmeter.samplers.Sampler,当前的采样器
  6. SampleResult:org.apache.jmeter.samplers.SampleResult,采样结果信息
  7. args:String,参数
  8. Parameters,String,参数
  9. FileName:文件名称
  10. prev:SampleResult,前一个采用器的结果
  11. Label:组件名称
  12. OUT:输出到Jmeter控制台,java.io.PrintStream
  13. AssertionResult:断言结果

这些信息都可以在脚本中查看,例如,我们一个数据库连接不上了,我们就可以通过props看一下是不是驱动有问题啊。

groovy 复制代码
props.each { key, value ->
    log.info("$key = $value")
}

这么多,很可能还会变,靠记肯定不靠谱,大致了解即可,知道有哪些东西,具体要用的时候,在脚本中打印一下可用的绑定key,和对应的类信息,然后去Jmeter的源码看有哪些方法可以使用:

groovy 复制代码
log.info("可用绑定键: " + binding.getVariables().keySet());
log.info("OUT:{},{}",OUT.getClass(),OUT.hashCode());
OUT.println("OUT println " + OUT.getClass())

Jmeter脚本实例

预处理参数

使用Jmeter脚本一个很重要的原因就是要处理各种参数,例如我们要压测大文件的场景,需要一个很大的json传给服务器,直接拷贝内容进body好像不太方便,这个时候就可以直接使用Jmeter的脚步来处理这种情况。

还有很多时候,我们多种场景需要从csv或者Excel中读取数据,可以通过Jmeter函数的方式实现,但是没有脚本的方式方便。

比如,我们有个一user.json内容如下:

json 复制代码
[{"id":0,"name":"tim_0"},{"id":1,"name":"tim_1"},{"id":2,"name":"tim_2"}]

怎么才能把这个文件的内容,作为请求的body参数传递给服务器呢?

很简单:

  1. 准备脚本,读取文件内容
  2. 通过Jmeter注入的vars对象把参数保存起来
  3. 在采样器中通过${param}的方式引用内容
groovy 复制代码
def readFile = new File("D:\\tmp\\user.json")
def param = readFile.getText()
log.info("读取文件参数:{}",param)
vars.put("jsonParam",param)

提取返回结果

服务端往往有自己的业务状态码,Jmeter HTTP请求默认会使用http的状态码来判断成功失败。

如何处理呢,我们可以通过后置处理器脚本来处理。

Jmeter lib中包含了jackson,所以可以直接使用。

groovy 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;

def body = prev.getResponseDataAsString()
log.info("服务器返回值:{}",body)

class Result{

    private Boolean success;

    private String message;

    private Integer code;

    private Object data;

    public Boolean getSuccess() {
        return success;
    }

    public void setSuccess(Boolean success) {
        this.success = success;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

ObjectMapper objectMapper = new ObjectMapper();
Result result = objectMapper.readValue(body, Result.class);
log.info("服务端code:{},服务端message:{}",result.code,result.message)

获取参数

我们可以通过sampler.getArguments()获取参数:

Query Data: id=123&name=J36

{"id":0,"name":"tim_0"},{"id":1,"name":"tim_1"},{"id":2,"name":"tim_2"}

groovy 复制代码
def readFile = new File("D:\\tmp\\user.json")
def param = readFile.getText()
log.info("读取文件参数:{}",param)
vars.put("jsonParam",param)
log.info("sampler:{},{}",sampler.getClass(),sampler)
log.info("getArguments:{}",sampler.getArguments())

header处理

除了通过HTTP请求头配置原件,还可以通过脚本的方式设置请求头:

groovy 复制代码
import org.apache.jmeter.protocol.http.control.Header
import org.apache.jmeter.protocol.http.control.HeaderManager

def readFile = new File("D:\\tmp\\user.json")
def param = readFile.getText()
log.info("读取文件参数:{}", param)
vars.put("jsonParam", param)

def hm = sampler.getHeaderManager()
log.info("hm:{}", hm)
// 预处理阶段还没有创建,手动创建
if (hm == null) {
    hm = new HeaderManager();
    sampler.setHeaderManager(hm);
}
hm.add(new Header("Content-Type", "application/json"))

接口签名

很多接口是有参数签名的,这种要怎么处理呢?

放心,Groovy还是能轻松处理这个问题,下面就是一个比较通用的规范实现,不侵入业务,基本拿来就能用,最多需要改一下参数名称。

对于接口签名不清楚的可以看一下:接口参数签名核心问题

groovy 复制代码
import org.apache.jmeter.protocol.http.control.Header
import org.apache.jmeter.protocol.http.control.HeaderManager
// Jmeter的lib包已经包含了apache common codec依赖的jar包,直接可以用
import org.apache.commons.codec.digest.DigestUtils

// 首先我们得自己添加时间戳和nonce这2个非业务参数
def nonce = UUID.randomUUID().toString()
def timestamp = System.currentTimeMillis()
sampler.addArgument("timestamp",String.valueOf(timestamp))
sampler.addArgument("nonce",nonce)

// 获取所有参数,包括我们添加到timestamp、nonce以及实际业务参数
def arguments = sampler.getArguments()
def argMap = arguments.getArgumentsAsMap()
// 签名密钥,不用传输只是参与计算签名
argMap.put("appSecret","123456qdd")

def treeMap = new TreeMap<String, String>(argMap)
StringJoiner stringJoiner = new StringJoiner("&")
Set<Map.Entry<String, String>> entries = treeMap.entrySet()
for (Map.Entry<String, String> entry : entries) {
    stringJoiner.add(entry.getKey() + "=" + entry.getValue())
}
String paramsStr = stringJoiner.toString()
def sign = DigestUtils.sha256Hex(paramsStr)

def hm = sampler.getHeaderManager()
if (hm == null) {
   hm = new HeaderManager();
   sampler.setHeaderManager(hm);
}
// 通常将签名放在header中,不和参数混在一起、避免额外处理
hm.add(new Header("sign",sign))

创建JSR223前置处理器,使用上面脚本就可以:

签名结果:

接口的示例代码:

java 复制代码
@RequestMapping("/sign")
public Result<?> sign(@RequestHeader("sign") String sign, @RequestParam Map<String, String> param) {
    String appSecret = "123456qdd";
    param.put("appSecret", appSecret);
    boolean flag = SignHelper.verifySign(sign, param);
    if (flag) {
        return Results.ofSuccess();
    } else {
        return Results.ofFail("非法请求");
    }
}
相关推荐
hgz071020 小时前
企业级Nginx反向代理与负载均衡实战
java·jmeter
无名小卒Rain21 小时前
Jmeter性能测试-用JSON表达式获取关联参数
jmeter·压力测试·性能测试
无名小卒Rain1 天前
Jmeter性能测试-用正则表达式取关联参数:单个正则模版和多个正则模版
jmeter·压力测试·性能测试
write19943 天前
02 jmeter常用组件
jmeter
天才测试猿3 天前
Jmeter 命令行压测&生成HTML测试报告
自动化测试·软件测试·python·测试工具·jmeter·职场和发展·jenkins
帝落若烟3 天前
jmeter下载安装-1
jmeter
古城小栈3 天前
性能测试:JMeter 压测 Spring Boot 微服务
spring boot·jmeter·微服务
2501_924064113 天前
2025年优测压测平台与JMeter效率成本对比及行业实践
jmeter·接口测试·压测方案
hgz07105 天前
Linux服务器环境部署与JMeter压测准备
linux·服务器·jmeter