性能测试工具 Jmeter 做 Http 接口测试 1:编写自定义函数

pengshenshen · 2018年03月15日 · 最后由 pengshenshen 回复于 2018年04月18日 · 3213 次阅读

开发 Jmeter 自定义函数

一、 前言

Jmeter 提供了一组函数可以帮我们在做性能测试的时候更好的做参数化,比如字符编码格式转换、读取运行时变量值等等。但是有时候我们需要根据业务进行自定义的函数开发。

比如最近一个项目,在发送 http 请求的时候,客户端需要发 Token,它是根据给项目分配的 ID 和 Key 值以及系统当前时间生成的,服务端会验证该 Token 的有效性。

开发自定义函数,网上一般有两种做法:

  • 修改 jmeter 源码,增加一个函数,重新导出 jmeter 的 jar 包,放入 jmeter 的 lib 目录。
  • 新建一个项目,导入 jmeter 原生 jar 包,继承虚基类,编写新函数,打包,放入 jmeter 的 lib 目录。

第一种方法既复杂,又不易于维护管理,我个人喜欢用第二种方法。

第二种方法的大坑就是 package 命名有特殊的要求:package 路径中必须包含 functions 一级。

二、 编写自定义函数的步骤

1. 新建一个工程,导入 jmeter jar 包。

我用 maven 管理项目,因此只需要在 pom 文件中加入以下引用。

<dependency>
     <groupId>org.apache.jmeter</groupId>
     <artifactId>ApacheJMeter_java</artifactId>
     <version>3.0</version>
 </dependency>
 <dependency>
     <groupId>org.apache.jmeter</groupId>
     <artifactId>ApacheJMeter_core</artifactId>
     <version>3.0</version>
 </dependency>
2. 新建 package:stressTest.functions

注意:路径中必须包含 functions,否则 jmeter 无法识别。JMeter 设计让一些核心的类(非 UI 相关的,比如 ApacheJMeter_core 等)可以在非 UI 的方式下运行的时候能被加载进来,这些类会被优先加载。加载这些类的时候是通过命名规则来实现的。所有实现 function 的类必需包含".functions."。
当然也可以通过更改 jmeter.properties 中的配置来实现改变命名规则,如下所示。但是一般来说不推荐更改此项配置。

classfinder.functions.contain=.functions.
3. 新建一个类继承 AbstractFunction,重写以下方法:
  • execute
  • setParameters
  • getReferenceKey
  • getArgumentDesc

这四个方法介绍如下:

public String execute(SampleResult previousResult, Sampler currentSampler) throws InvalidVariableException

JMeter 会将上次运行的 SampleResult 和当前的 Sampler 作为参数传入到该方法里,返回值就是在运行该 function 后得到的值,以 String 类型返回。该方法如果操作了非线程安全的对象(比如文件),则需要将对该方法进行线程同步保护。

public void setParameters(Collection<CompoundVariable> parameters) throws InvalidVariableException;

这个方法在用于传递用户在执行过程当中传入的实际参数值。该方法在 function 没有参数情况下也会被调用。一般该方法传入的参数会被保存在类内全局变量里,并被后面调用的 execute 方法中使用到。

public String getReferenceKey();

这个就是 function 的名字。JMeter 的命名规则是在方法名前面加入双下划线"__"。比如"__GetEven",function 的名字跟实现该类的类名应该一致,而且该名字应该以 static final 的方式在实现类中定义好,避免在运行的时候更改它。

public List<String> getArgumentDesc();

最后在你的实现类中还需要提供一个方法来告诉 JMeter 关于你实现的 function 的描述。

我自己编写的 GetToken 类如下:

package stressTest.functions;

import org.apache.jmeter.engine.util.CompoundVariable;
import org.apache.jmeter.functions.AbstractFunction;
import org.apache.jmeter.functions.InvalidVariableException;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.util.JMeterUtils;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.util.encoders.Hex;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

/**
 * Created by pengshenshen on 2018/3/12.
 */
public class GetToken extends AbstractFunction {

    private static final List<String> desc = new LinkedList<String>();
    private static final String KEY = "__getToken";
    private CompoundVariable[] values;

    // Number of parameters expected - used to reject invalid calls
    private static final int MIN_PARAMETER_COUNT = 2;
    private static final int MAX_PARAMETER_COUNT = 3;

    static {
        desc.add(JMeterUtils.getResString("appId"));
        desc.add(JMeterUtils.getResString("apiKey"));
    }

    @Override
    public String execute(SampleResult sampleResult, Sampler sampler) throws InvalidVariableException {
        String appId = values[0].execute();
        String apiKey = values[1].execute();
        long currentTimeMillis = System.currentTimeMillis();
        // 5 minutes
        currentTimeMillis = currentTimeMillis - (currentTimeMillis % (1000 * 60 * 5));
        byte[] data = (appId + apiKey + currentTimeMillis).getBytes();
        MD5Digest md5 = new MD5Digest();
        md5.update(data, 0, data.length);
        byte[] digest = new byte[md5.getDigestSize()];
        md5.doFinal(digest, 0);
        return Hex.toHexString(digest);
    }

    @Override
    public void setParameters(Collection<CompoundVariable> parameters) throws InvalidVariableException {
        checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT);
        values = parameters.toArray(new CompoundVariable[parameters.size()]);
    }

    @Override
    public String getReferenceKey() {
        return KEY;
    }

    @Override
    public List<String> getArgumentDesc() {
        return desc;
    }
}

4. 打包

我用 maven-assembly-plugin 打包。方法如下:
首先在 pom 文件中引入插件:

<plugin>
<artifactId>maven-assembly-plugin</artifactId><configuration>
    <descriptorRefs>
        <descriptorRef>jar-with-dependencies</descriptorRef>
    </descriptorRefs>
</configuration>
</plugin>

然后执行打包命令:mvn clean compile assembly:single

5. 将打出来的 jar 包拷贝至 jmeter 的目录:

将打出来的 jar 包拷贝至 jmeter 的目录:Jmerter_HOME\lib\ext

6. 运行

Jmerter_HOME\bin\jmeter.bat,在函数助手界面中可以找到我的函数:

7. 测试

随便做一个 post 请求,测试该方法是否有效

向百度发一个 post 请求,查看一下 body 里面传 getToken 函数的结果

如图:在查看结果树中看到:请求的 body 中已经带了 Token 值。

以上函数介绍部分参照:https://www.jianshu.com/p/a88e5cb1d6cb (这个作者有产出不少 jmeter 的文章,在此标明出处,以后也可以慢慢看)

共收到 5 条回复 时间 点赞

沙发,顶顶~~

林小五 回复

喵喵~

第 6 和第 7 点里面的图片挂了,修复下?

陈恒捷 回复

多谢,可能是图片引的外链不稳定,重新上传了。

记录一下,assembly 插件有个 bug,打包时管理的 spring 的 xsd 文件有问题,会导致 spring 启动时在不联网的情况下找不到一些 xsd 文件(联网的情况下会联网找,没有问题)
具体描述如:http://chenzhou123520.iteye.com/blog/1706242
因此后来我都换 shade 插件了。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册