JMeter取样器插件开发

背景:**需用通过jmeter这个测试工具去测试某系统接口,该系统已知有自己的sdk和自己的协议,暴露出来的接口还是c++开发的,具体的还没涉及到,总之jmeter现有提供的功能不满足测试要求,需要在jmeter上开发出新的插件。
调查:插件开发这个说法比较广泛,这个范围太大了,对着现有功能的页面分析了下,需要开发出自定义取样器,自定义页面来接收测试人员录入的参数,启动测试,执行自定义的接口逻辑处理(具体的处理逻辑还待定),然后根据要求返回出结果值。
初步目标:按照jmeter的sample开发要求,自定义界面布局,将自定义界面值传入自定义处理逻辑中。
参考网址
之前辛辛苦苦搭建起来的jmeter开发环境发现用不到了,因为sample插件开发,是maven功能里做的,然后打成jar包,放入\lib\ext下,就可以使用了。

1、新建maven工程
2、pom文件引入jmeter的核心包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>jmeterplugintest</groupId>
    <artifactId>jmeterplugintest</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>jmeterplugntest</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jmeter-version>3.1</jmeter-version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.jmeter</groupId>
            <artifactId>ApacheJMeter_core</artifactId>
            <version>${jmeter-version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.jmeter</groupId>
            <artifactId>ApacheJMeter_java</artifactId>
            <version>${jmeter-version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>ssmtest</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-site-plugin</artifactId>
                <version>3.7</version>
                <dependencies>
                    <dependency>
                        <groupId>org.apache.maven.doxia</groupId>
                        <artifactId>doxia-site-renderer</artifactId>
                        <version>1.8</version>
                    </dependency>
                </dependencies>
            </plugin>
         <!-- 这块用来控制maven打包,是否将依赖的包打进来
           <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.5.5</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.yj.TCPClient.upload.App</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>-->
                <!-- 添加此项后,可直接使用mvn package | mvn install -->
                <!-- 不添加此项,需直接使用mvn package assembly:single -->
            <!-- <executions>
                 <execution>
                     <id>make-assembly</id>
                     <phase>package</phase>
                     <goals>
                         <goal>single</goal>
                     </goals>
                 </execution>
             </executions>
         </plugin>
          -->
        </plugins>
    </build>

</project>

3、代码开发,分两块,一块是 界面布局开发,继承AbstractSamplerGui,用来设置界面控件和布局;另一块取样器逻辑开发,继承AbstractSampler,用来处理数据、请求接口、返回值处理。
MyPluginGUI.java

package com.test.gui;

import com.test.sampler.MyPluginSampler;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.config.gui.ArgumentsPanel;
import org.apache.jmeter.gui.util.HorizontalPanel;
import org.apache.jmeter.gui.util.JSyntaxTextArea;
import org.apache.jmeter.gui.util.JTextScrollPane;
import org.apache.jmeter.gui.util.VerticalPanel;
import org.apache.jmeter.protocol.java.config.JavaConfig;
import org.apache.jmeter.protocol.java.sampler.JavaSampler;
import org.apache.jmeter.samplers.gui.AbstractSamplerGui;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.property.BooleanProperty;
import org.apache.jmeter.testelement.property.TestElementProperty;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.gui.JLabeledChoice;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

public class MyPluginGUI extends AbstractSamplerGui implements ItemListener {
    private  static  final long serialVersionUID=20l;

    //IP
    private JTextField domain;
    //端口号
    private JTextField port;
    //encoding
    private JTextField contentEncoding;
    //路径
    private JTextField path;
    //keepalive
    private JCheckBox useKeepAlive;
    //method POST,GET
    private JLabeledChoice method;
    /** A panel allowing the user to set arguments for this test. */
    //动态可添加参数
    private ArgumentsPanel argsPanel;

    //IP页面设置
    private JPanel getDomainPanel(){
        domain=new JTextField(10);
        JLabel label=new JLabel("IP");
        label.setLabelFor(domain);

        JPanel panel=new HorizontalPanel();
        panel.add(label, BorderLayout.WEST);
        panel.add(domain,BorderLayout.CENTER);
        return panel;
    }

    //端口设置
    private JPanel getPortPanel(){
        port=new JTextField(10);
        JLabel label=new JLabel(JMeterUtils.getResString("web_server_port"));
        label.setLabelFor(port);

        JPanel panel=new HorizontalPanel();
        panel.add(label,BorderLayout.WEST);
        panel.add(port,BorderLayout.CENTER);
        return  panel;
    }

    //contentEncoding
    private JPanel getContentEncodingPanel(){
        contentEncoding=new JTextField(10);
        JLabel label=new JLabel("contentEncoding");
        label.setLabelFor(contentEncoding);

        JPanel panel=new HorizontalPanel();
        panel.add(label,BorderLayout.WEST);
        panel.add(contentEncoding,BorderLayout.CENTER);
        return panel;
    }

    //路径
    private JPanel getPathPanel(){
        path=new JTextField(10);
        JLabel label=new JLabel(JMeterUtils.getResString("path"));
        label.setLabelFor(path);

        JPanel panel=new HorizontalPanel();
        panel.add(label,BorderLayout.WEST);
        panel.add(path,BorderLayout.CENTER);
        return panel;
    }

    //keepalive 和 method
    private Component getMethodAndKeepAlive(){
        useKeepAlive =new JCheckBox(JMeterUtils.getResString("use_keepalive"));
        useKeepAlive.setFont(null);
        useKeepAlive.setSelected(true);

        JPanel optionPanel=new HorizontalPanel();
        optionPanel.setMinimumSize(optionPanel.getPreferredSize());
        optionPanel.add(useKeepAlive);

        String Marry[]={"GET","POST"};
        method=new JLabeledChoice(JMeterUtils.getResString("method"),Marry,true,false);
        JPanel methodPanel=new HorizontalPanel();
        methodPanel.setLayout(new BoxLayout(methodPanel,BoxLayout.Y_AXIS));
        methodPanel.add(optionPanel,BorderLayout.WEST);
        methodPanel.add(method,BorderLayout.WEST);
        return methodPanel;
    }


    /**
     * Create a panel containing components allowing the user to provide
     * arguments to be passed to the test class instance.
     *
     * @return a panel containing the relevant components
     */
    private JPanel createParameterPanel() {
        argsPanel = new ArgumentsPanel(JMeterUtils.getResString("paramtable")); // $NON-NLS-1$
        return argsPanel;
    }

    //页面构建
    public void createPanel(){
        JPanel settingPanel=new VerticalPanel();
        settingPanel.add(getDomainPanel());
        settingPanel.add(getPortPanel());
        settingPanel.add(getContentEncodingPanel());
        settingPanel.add(getPathPanel());
        settingPanel.add(getMethodAndKeepAlive());
//        settingPanel.add(getPostBodyContent());

        JPanel dataPanel=new JPanel(new BorderLayout(5,0));
        dataPanel.add(settingPanel,BorderLayout.NORTH);
        dataPanel.add(createParameterPanel(), BorderLayout.CENTER);

        setLayout(new BorderLayout(0,5));
        setBorder(makeBorder());
        add(makeTitlePanel(),BorderLayout.NORTH);
        add(dataPanel,BorderLayout.CENTER);
    }

    private void init(){
        createPanel();
    }

    public MyPluginGUI(){
        super();
        init();
    }

    @Override
    public String getLabelResource() {
        throw new IllegalStateException("this is should not  be called");
    }

    @Override
    public String getStaticLabel(){
        return "sample";
    }

    @Override
    public void clearGui(){
        super.clearGui();
        argsPanel.clearGui();

        domain.setText("");
        port.setText("");
        contentEncoding.setText("");
        path.setText("");
//        postBodyContent.setText("");
        method.setText("GET");
        useKeepAlive.setSelected(true);
    }

    /**
     * 创建一个sampler,然后将界面中的数据设置到这个新的sampler实例中
     * @return
     */
    @Override
    public TestElement createTestElement() {
       MyPluginSampler sampler=new MyPluginSampler();
       modifyTestElement(sampler);
       return sampler;
    }

    /**
     * 把界面上的数据移到sample中
     * @param testElement
     */
    @Override
    public void modifyTestElement(TestElement testElement) {
        testElement.clear();
        configureTestElement(testElement);
        //键值对
        testElement.setProperty(new TestElementProperty(MyPluginSampler.ARGUMENTS, (Arguments) argsPanel.createTestElement()));
        testElement.setProperty(MyPluginSampler.domain,domain.getText());
        testElement.setProperty(MyPluginSampler.port,port.getText());
        testElement.setProperty(MyPluginSampler.contentEncoding,contentEncoding.getText());
        testElement.setProperty(MyPluginSampler.path,path.getText());
        testElement.setProperty(MyPluginSampler.useKeepAlive,useKeepAlive.getText());
//        testElement.setProperty(MyPluginSampler.postBodyContent,postBodyContent.getText());
        testElement.setProperty(new BooleanProperty(MyPluginSampler.useKeepAlive,useKeepAlive.isSelected()));
        testElement.setProperty(MyPluginSampler.method,method.getText());
    }

    @Override
    public void configure(TestElement element){
        super.configure(element);
        //jmeter运行后,保存参数,不然执行后,输入框会清空
        argsPanel.configure((Arguments) element.getProperty(MyPluginSampler.ARGUMENTS).getObjectValue());
        domain.setText(element.getPropertyAsString(MyPluginSampler.domain));
        port.setText(element.getPropertyAsString(MyPluginSampler.port));
        contentEncoding.setText(element.getPropertyAsString(MyPluginSampler.contentEncoding));
        path.setText(element.getPropertyAsString(MyPluginSampler.path));
//        postBodyContent.setText(element.getPropertyAsString(MyPluginSampler.postBodyContent));
        method.setText("GET");
        useKeepAlive.setSelected(true);


    }

    /**
     * Invoked when an item has been selected or deselected by the user.
     * The code written for this method performs the operations
     * that need to occur when an item is selected (or deselected).
     *
     * @param e
     */
    @Override
    public void itemStateChanged(ItemEvent e) {

    }
}

注:ArgumentsPanel 为下面表格参数页面,可动态添加,这个找了好一会儿才寻来的

```clike
MyPluginSampler.java
package com.test.sampler;

import net.minidev.json.JSONObject;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;

import java.io.Serializable;
import java.util.concurrent.atomic.AtomicInteger;

public class MyPluginSampler extends AbstractSampler implements Serializable {
    private static  final  long   serialVersionUID=230l;

    private static  final Logger log= LoggingManager.getLoggerForClass();
    //页面属性数据存放
    public static final String domain="domain.text";
    public static final String port="port.text";
    public static final String contentEncoding="contentEncoding.text";
    public static final String path="path.text";
    public static final String method="method.text";
    public static final String useKeepAlive="useKeepAlive.text";
//    public static final String postBodyContent="postBodyContent.text";
    public static final String ARGUMENTS = "mypluginSampler.Arguments"; // $NON-NLS-1$


    private static AtomicInteger classCount=new AtomicInteger(0);//keep track of classes created

    private String getTitle(){
        return this.getName();
    }

    //从控制面板获取domain输入的数据
    public String getDomain(){
        return getPropertyAsString(domain);
    }
    //从控制面板获取端口的输入的数据
    public String getPort(){
        return getPropertyAsString(port);
    }
    //从控制面板获取contentEncoding输入的数据
    public String getContentEncoding(){
        return getPropertyAsString(contentEncoding);
    }
    //从控制面板获取path输入的数据
    public String getPath(){
        return  getPropertyAsString(path);
    }
    //从gui获取method输入的数据
    public String getMethod(){
        return getPropertyAsString(method);
    }
    //从gui获取useKeepAlive的值
    public  String getUseKeepAlive(){
        return getPropertyAsString(useKeepAlive);
    }
//    //从gui获取postBody输入的数据
//    public String getPostBodyContent(){
//        return getPropertyAsString(postBodyContent);
//    }
   public Arguments getArguments() {
     return (Arguments) getProperty(ARGUMENTS).getObjectValue();
   }



    public MyPluginSampler(){
        setName("sampler练习示例");
        classCount.incrementAndGet();
        trace("第一个sampler练习");
    }

    private void trace(String s){
        String t1=getTitle();
        String tn=Thread.currentThread().getName();
        String th=this.toString();

        log.debug(tn+" ("+classCount.get()+") "+t1+" "+s+" "+th);
    }

    @Override
    public SampleResult sample(Entry entry) {
        trace("sample()");
        SampleResult res=new SampleResult();
        boolean isOK=false;//did sample is succeed?

        String response=null;
        //sampler data
        String domain=getDomain();
        String port=getPort();
        String contentEncoding=getContentEncoding();
        String path=getPath();
        String useKeepAlive=getUseKeepAlive();
        String method=getMethod();
//        String postBodyContent=getPostBodyContent();
        Arguments arguments=getArguments();

        JSONObject jo=new JSONObject();
        jo.put("domain",domain);
        jo.put("port",port);
        jo.put("contentEncoding",contentEncoding);
        jo.put("path",path);
        jo.put("useKeepAlive",useKeepAlive);
        jo.put("method",method);
        jo.put("arguments",arguments.toString());

        res.setSampleLabel(getTitle());
        /**
         * perform sampler resultData
         */
        res.sampleStart();
        try{
            response=Thread.currentThread().getName();
            res.setSamplerData("setSamleData!!!");
            res.setResponseData(jo.toString(),null);
            log.debug("json:"+jo.toString());
            res.setDataType(SampleResult.TEXT);

            res.setResponseCodeOK();
            res.setResponseMessage("OK");
            isOK=true;
        }catch (Exception e){
            log.debug(" ",e);
            res.setResponseCode("500");
            res.setResponseMessage(e.toString());
        }
        res.sampleEnd();
        res.setSuccessful(isOK);
        return res;
    }
}

主要的是override几个方法,实现自己的处理逻辑,现在只是数据串通,后面等明确需求再完善。

4、打成jar包,放入jemeter lib\ext下,启动

jemter取样结果_jmeter

5、启动执行看效果

jemter取样结果_java_02

看结果:串通起来了。

jemter取样结果_java_03