【java学习】jmeter与自动化测试

本文详细介绍了JMeter在自动化测试中的应用,包括自动化测试规范、常见测试场景、JMeter语法使用,如事务、循环、IF判断、HTTP取样器等,并探讨了插件编写与本地调试的方法,为提升测试效率提供了实用指导。

1,自动化测试规范

1)可重复执行

  1. 可重复执行
    每个用例的数据应该是独立的,测试前新增,测试后删除;
  2. 可并行执行
    每个用例数据的建立应该是独立的,即使其它数据在并发执行应该互不干扰。==》通过uuid或者用例号对数据进行区分。
  3. 如果用例执行错误,不会造成脏数据。

2)可移植性

  1. 不同环境都可以执行;
    主要区别在于当前使用的数据是否在不同环境是一样的;
    主要问题集中在id的引用。

3)可控性

  1. 用例不会无限执行下去;
    重灾区是循环用例。
  2. 用例响应时间(用例偶尔执行失败)
    主要针对2个方面:
    1)连续2个请求,请求A入库,请求B查询A的结果。A的入库可能是异步的,也就是说A返回200时,B不一定能查询到数据。此时可能需要sleep一会再处理,这个sleep的时间是否会因为环境负载导致时间有极大的波动?
    2)对于一些时间的操作,接口的自动化执行远远快于人工的点击,完全可能因为ms级别的误差导致用例偶尔执行失败。
    举例:获取用例执行开始时当前时间stime;创建一条数据createTime;检索当前数据:在stime和当前检索时间之间。由于用例请求过快,导致stime和createTime都是s单位一致、ms不同,如果检索的时间精度是s,那么就会出现stime精度丢失从而导致createTime>stime的情况。导致用例偶尔执行失败。
  3. 对于复杂逻辑,统一处理
    比如循环请求一个http接口,需要注意while条件。但是这个很容易出错,每个人的水平也不一样,不如编写一个轮询http插件进行处理,简化流程。

4)可校验

  1. 所有的操作应该都是可校验的。
    我看有一下接口只请求了连200校验都没有。

2,自动化测试场景

  1. 测试时间长,但主要是机械重复动作的测试;
  2. 需求变更频繁且每次变更影响面大;
  3. 测试需要大量回归测试;

除了使用自动化测试用例,也要标记一些重要用例进行人工测试。

3,常见语法使用

1)用例准备

1>函数助手

Ctrl+Shift+F1

常用函数:

说明函数
当前日期${__timeShift(yyyyMMdd,,,,)}
昨天日期${__timeShift(yyyyMMdd,,-P1D,,)}

2>用户自定义变量

  1. 定义一次,全局享用
  2. 通过函数获取值
名称
time${__time(,)}
  1. 通过提前声明控制变量类型
    如果在beanShell里之间vars.put(),这个变量会是字符串。
名称
times0
  1. 在beanShell中操作
    注意对象在vars中是以String的形式存放的。
String str = vars.get("time")
vars.put("time", str)
  1. 自定义插件中vars的操作
JMeterContext context = JMeterContextService.getContext();
JMeterVariables vars = context.getVariables();
jmxPathStr = FileServer.getFileServer().getBaseDir();
sepStr = System.getProperty("file.separator");
vars.put("jmxPath", jmxPathStr);
vars.put("sep", sepStr);
context.setVariables(vars);

3>HTTP请求默认值

  1. 全局生效
  2. 主要用于定义统一的http请求协议、ip、端口、编码格式
    在这里插入图片描述
  3. 如果定义了utf-8还乱码,可能是http对应的jar包有问题
    替换\lib\ext\ApacheJMeter_http.jar

4>HTTP信息管理器

名称说明
Accept-Languagezh-CN,zh;q=0.9,en;q=0.8
Content-Typeapplication/json;charset=UTF-8
Accept-Encodinggzip, deflate, br
User-AgentMozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36浏览器
acceptapplication/json
xxxx-Access-Token${token}访问token,登录成功后直接赋值,全局生效

5>JDBC连接设置

其中连接名称是mytest,jdbc取样器时需要使用。
在这里插入图片描述

6>HTTP Cookie管理器

直接添加 自动管理

1)事务

是一个简单的控制器,作为整体包裹我们需要关注的一组操作。
只有当事务控制器所有的取样器都成功时,事务控制器才成功。

2)循环

1>foreach控制器

在这里插入图片描述

2>while控制器

在这里插入图片描述

3)IF

在这里插入图片描述
jmeter-jexl3语法总结:

描述语法
数字等于${__jexl3(${__Random(0,500,)}==0,)}
数字不等于${__jexl3(${__Random(0,500,)}!=0,)}
字符串相等${__jexl3("qwe".equals("qwe"),)}
${__jexl3("qwe" eq "qwe",)}
${__jexl3("qwe" == "qwe",)}
字符串不相等${__jexl3(!"qwe".equals("q1we"),)}
${__jexl3("qwe" ne "qwe1",)}
${__jexl3("qwe" != "qwe",)}
字符串包含${__jexl3("${host}".contains("pre"),)}
字符串不包含${__jexl3(!"${host}".contains("pre"),)}
字符串长度比较${__jexl3("qqq".size() < 3,)}
length()也可以
字符串为空${__jexl3("".empty(),re)}
组合条件通过双竖杠连接

4)http取样器

在这里插入图片描述注意:

  1. 文件上传类的接口:
    在这里插入图片描述

  2. url参数
    可以直接在参数列表添加,也可以直接写url中。直接通过${}引用变量。

5)JDBC Request

数据库请求取样器,使用前需要先配置JDBC Connection Configuration。
在这里插入图片描述

6)响应断言

在这里插入图片描述相比起来json断言可能比响应文本断言更好用。

7)json断言

通过jsonPath语法进行取值。
在这里插入图片描述
在这里插入图片描述

8)BeanShell 取样器

程序员的天下了。

  1. 取变量通过vars变量才能获取,不能直接使用${}
  2. 没有泛型
  3. 可以使用方法
  4. 外部引用需要import包
  5. 调试方法:log、或者下载源码通过IDEA运行本地调试。

9)BeanShell 断言

  1. 在BeanShell 取样器的基础上,增加了断言返回值的重写。
  2. 断言之后可以直接return;,否则断言会继续往后走
	//表示断言失败。
	Failure = true;
	//错误提示
	FailureMessage = 数据库查询与返回结果不一致

10)前置后置执行顺序

  1. 配置元件(Config Element)
  2. 前置处理器(Pre Processors)
  3. 定时器(Timer)
  4. 取样器(Sampler)
  5. 后置处理器(Post Processors)
  6. 断言(Assertions)
  7. 监听器(Listener)

11)结果调试

在这里插入图片描述

1>结果集(查看结果树)

运行脚本之后生成JTL文件。红色表示失败。
在这里插入图片描述

2>日志

在这里插入图片描述
beanShell里可以通过log直接打印日志。

3>调试取样器

添加调试取样器可以查看到运行过程中的变量,变量是可以被覆盖的。

4,常见插件编写

1)取样器重写:轮询http请求

循环请求某个接口,默认循环10次,每两次间隔500ms。通过jsonPath来判断是否需要立刻停止循环。

package com.luo.sampler.gui;

import com.luo.sampler.PollingHttpSampler;
import org.apache.jmeter.gui.GUIMenuSortOrder;
import org.apache.jmeter.gui.util.HorizontalPanel;
import org.apache.jmeter.gui.util.VerticalPanel;
import org.apache.jmeter.samplers.gui.AbstractSamplerGui;
import org.apache.jmeter.testelement.TestElement;

import javax.swing.*;
import java.awt.*;
import java.util.HashMap;
import java.util.Map;

@GUIMenuSortOrder(1)
public class PollingHttpSamplerGui extends AbstractSamplerGui {

    /**
     * 请求类型 GET
     */
    private static final Map<String, Class<?>> TYPE_HTTP = new HashMap<>(1);


    static {
        TYPE_HTTP.put("GET", String.class);
    }

    protected JComboBox<String> typeBox = null;
    /**
     * 请求路径
     */
    private JTextField urlPath = null;
    /**
     * 轮询次数
     */
    private JTextField times = null;
    /**
     * 轮询结束字段(json path,如:$.data.percent)
     */
    private JTextField endStateField = null;
    private JTextField endStateValue = null;

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

    private void init() {
        createPanel();
    }

    private void createPanel() {
        JPanel settingPanel = new VerticalPanel(5, 0);
        settingPanel.add(getUrlPath());
        settingPanel.add(getTimes());
        settingPanel.add(getEndState());

        JPanel dataPanel = new JPanel(new BorderLayout(5, 0));
        dataPanel.add(settingPanel, BorderLayout.NORTH);
        setLayout(new BorderLayout(0, 8));
        setBorder(makeBorder());
        add(makeTitlePanel(), BorderLayout.NORTH); // Add the standard title
        add(dataPanel, BorderLayout.CENTER);
    }

    private JPanel getUrlPath() {
        typeBox = new JComboBox<>();
        typeBox.addItem("GET");
        urlPath = new JTextField(10);
        JLabel label = new JLabel("请求路径:");
        label.setLabelFor(urlPath);

        JPanel panel = new HorizontalPanel();
        panel.add(typeBox);
        panel.add(label);
        panel.add(urlPath);
        return panel;
    }

    private JPanel getTimes() {
        times = new JTextField(10);
        times.setText("10");
        JLabel label = new JLabel("轮询次数(2次间隔500ms):");
        label.setLabelFor(times);

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

    private JPanel getEndState() {
        endStateField = new JTextField(10);
        endStateField.setText("$.data.percent");
        JLabel label = new JLabel("轮询结束字段($.data.percent):");
        label.setLabelFor(endStateField);

        endStateValue = new JTextField(10);
        endStateValue.setText("100");
        JLabel label1 = new JLabel("=");
        label1.setLabelFor(endStateValue);

        JPanel panel = new HorizontalPanel();
        panel.add(label);
        panel.add(endStateField);
        panel.add(label1);
        panel.add(endStateValue);
        return panel;
    }

    @Override
    public String getLabelResource() {        // TODO Auto-generated method stub
        throw new IllegalStateException("This shouldn't be called");
    }

    @Override
    public TestElement createTestElement() {        // TODO Auto-generated method stub
        PollingHttpSampler sampler = new PollingHttpSampler();
        modifyTestElement(sampler);
        return sampler;
    }

    @Override
    public void modifyTestElement(TestElement testElement) {
        testElement.clear();
        configureTestElement(testElement);
        testElement.setProperty(PollingHttpSampler.urlPath, urlPath.getText());
        testElement.setProperty(PollingHttpSampler.times, times.getText());
        testElement.setProperty(PollingHttpSampler.endStateField, endStateField.getText());
        testElement.setProperty(PollingHttpSampler.endStateValue, endStateValue.getText());
    }

    @Override
    public void configure(TestElement el) {
        super.configure(el);
        PollingHttpSampler sampler = (PollingHttpSampler) el;
        this.urlPath.setText(sampler.getUrlPath());
        this.times.setText(sampler.getTimes());
        this.endStateField.setText(sampler.getEndStateField());
        this.endStateValue.setText(sampler.getEndStateValue());
    }

    @Override
    public String getStaticLabel() {
        return "轮询Http请求";
    }

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

package com.luo.jmeter.sampler;

import com.alibaba.fastjson.JSONObject;
import com.jayway.jsonpath.JsonPath;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.threads.JMeterVariables;

import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;

public class PollingHttpSampler extends AbstractSampler {
    public static final String urlPath = "urlPath.text";
    public static final String times = "times.text";
    public static final String endStateField = "endStateField.text";
    public static final String endStateValue = "endStateValue.text";

    private String ip = "";
    private String token = "";

    private void init() {
        JMeterContext context = JMeterContextService.getContext();
        JMeterVariables vars = context.getVariables();
        ip = vars.get("ip");
        token = vars.get("token");
    }

    private boolean isEnd(JSONObject jsonObject) {
        String endField = "";
        Object percent = JsonPath.read(jsonObject, getEndStateField());
        switch (percent.getClass().getName()) {
            case "java.math.BigDecimal":
                endField = String.valueOf(((Number) percent).intValue());
                break;
            default:
                endField = String.valueOf(percent);
        }
        int index = endField.indexOf('.');
        if (index != -1) {
            endField = endField.substring(0, index);
        }
        return Objects.equals(endField, getEndStateValue());
    }

    @Override
    public SampleResult sample(Entry entry) {
        SampleResult mainRes = new SampleResult();
        mainRes.setSampleLabel(getTitle());
        mainRes.sampleStart();  //获取start time
        try {
            init();
            int times = 0;
            JSONObject result = null;
            boolean isSuccess = false;
            while (times <= Integer.valueOf(getTimes())) {
                result = getHttpProgress();
                if (isEnd(result)) {
                    isSuccess = true;
                    break;
                }
                Thread.sleep(500);
                ++times;
            }
            result.put("times", times);
            if (isSuccess) {

                mainRes.setResponseData(JSONObject.toJSONString(result), "utf-8");
                mainRes.sampleEnd();
                mainRes.setSuccessful(true);
                mainRes.setResponseCodeOK();
                mainRes.setResponseMessage("获取成功!");
            } else {

                mainRes.setResponseData(JSONObject.toJSONString(result), "utf-8");
                mainRes.sampleEnd();
                mainRes.setSuccessful(false);
                mainRes.setResponseCodeOK();
                mainRes.setResponseMessage("循环" + times + "次未结束");
            }
        } catch (Exception e) {
            mainRes.sampleEnd();
            mainRes.setSuccessful(false);
            mainRes.setResponseMessage("Exception: " + e);
            java.io.StringWriter stringWriter = new java.io.StringWriter();
            e.printStackTrace(new java.io.PrintWriter(stringWriter));
            mainRes.setResponseData(stringWriter.toString(), null);
            mainRes.setDataType(SampleResult.TEXT);
            mainRes.setResponseCode("FAILED");
        }
        return mainRes;
    }
    private String getTitle() {
        return this.getName();
    }

    public String getUrlPath() {
        return getPropertyAsString(urlPath);
    }

    public String getTimes() {
        return getPropertyAsString(times);
    }

    public String getEndStateField() {
        return getPropertyAsString(endStateField);
    }

    public String getEndStateValue() {
        return getPropertyAsString(endStateValue);
    }
}

2)断言重写:文件下载断言

package com.luo.jmeter.assertions.gui;

import com.luo.jmeter.assertions.FileDownloadAssertion;
import org.apache.jmeter.assertions.gui.AbstractAssertionGui;
import org.apache.jmeter.gui.GUIMenuSortOrder;
import org.apache.jmeter.gui.util.HorizontalPanel;
import org.apache.jmeter.gui.util.VerticalPanel;
import org.apache.jmeter.testelement.TestElement;

import javax.swing.*;
import java.awt.*;

/**
 * 文件下载断言
 */
@GUIMenuSortOrder(1)
public class FileDownloadAssertionGui extends AbstractAssertionGui {


    protected JTextField path = null;
    protected JTextField fileName = null;

    public FileDownloadAssertionGui() {
        this.init();
    }

    private void init() {
        JPanel settingPanel = new VerticalPanel(5, 0);
        settingPanel.add(getPath());
        settingPanel.add(getFileName());

        JPanel dataPanel = new JPanel(new BorderLayout(5, 0));
        dataPanel.add(settingPanel, BorderLayout.NORTH);
        setLayout(new BorderLayout(0, 8));
        setBorder(makeBorder());
        add(makeTitlePanel(), BorderLayout.NORTH); // Add the standard title
        add(dataPanel, BorderLayout.CENTER);
    }

    private JPanel getPath() {
        path = new JTextField(10);
        path.setText("./download_files");
        path.setEditable(false);
        JLabel label = new JLabel("下载文件路径:");
        label.setLabelFor(path);

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

    private JPanel getFileName() {
        fileName = new JTextField(10);
        fileName.setText("下载文件.xlsx");
        JLabel label = new JLabel("下载文件名(zip会自动解压校验):");
        label.setLabelFor(fileName);

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

    @Override
    public String getLabelResource() {
        return null;
    }

    @Override
    public String getStaticLabel() {
        return "文件下载断言";
    }

    @Override
    public TestElement createTestElement() {
        FileDownloadAssertion assertion = new FileDownloadAssertion();
        this.modifyTestElement(assertion);
        return assertion;
    }

    @Override
    public void modifyTestElement(TestElement element) {
        super.configureTestElement(element);
        FileDownloadAssertion assertion = (FileDownloadAssertion) element;
        assertion.setFileName(this.fileName.getText());
    }

    @Override
    public void configure(TestElement element) {
        super.configure(element);
        FileDownloadAssertion assertion = (FileDownloadAssertion) element;
        this.fileName.setText(assertion.getFileName());
    }

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

}

package com.luo.jmeter.assertions;

import org.apache.jmeter.assertions.Assertion;
import org.apache.jmeter.assertions.AssertionResult;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.services.FileServer;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;

import java.io.*;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class FileDownloadAssertion extends AbstractTestElement implements Serializable, Assertion {


    private static final long seralVersionUID = 24L;

    private static final String fileName = "fileName.text";
    private String path = null;

    @Override
    public AssertionResult getResult(SampleResult response) {

        String filePath = downloadFile();

        AssertionResult result = new AssertionResult(getName());
        if (fileExist(filePath) && decompress(filePath)) {

            result.setFailureMessage("下载文件存在");
            result.setError(false);
            result.setFailure(false);
        } else {

            result.setFailureMessage("下载文件不存在");
            result.setError(true);
            result.setFailure(true);
        }
        return result;
    }

    private String downloadFile() {

        String jmxPath = FileServer.getFileServer().getBaseDir();
        String sep = System.getProperty("file.separator");

        JMeterContext context = JMeterContextService.getContext();
        SampleResult sampleResult = context.getPreviousResult();
        byte[] result = sampleResult.getResponseData();
        String filePath = jmxPath + sep + "download_files" + sep + getFileName();
        path = jmxPath + sep + "download_files" + sep;

        File file = new File(filePath);
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(file);
            out.write(result);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return filePath;
    }

    public String getFileName() {
        return this.getPropertyAsString(fileName);
    }

    public void setFileName(String jsonPath) {
        this.setProperty(fileName, jsonPath);
    }

}

5,插件本地调试

1)maven install

customize-extend-plug-1.0-SNAPSHOT.jar包放入jmeter的目录:lib\ext中

2)jmeter.bat配置启动参数

On Windows

set JVM_ARGS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000

on Linux/Unix/MacOX

JVM_ARGS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 && export JVM_ARGS

jmeter -n -t {path_to_your_jmx_scipt} -l {path_to_jtl_results_file}

3)IDEA配置远程调试

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值