1,自动化测试规范
1)可重复执行
- 可重复执行
每个用例的数据应该是独立的,测试前新增,测试后删除; - 可并行执行
每个用例数据的建立应该是独立的,即使其它数据在并发执行应该互不干扰。==》通过uuid或者用例号对数据进行区分。 - 如果用例执行错误,不会造成脏数据。
2)可移植性
- 不同环境都可以执行;
主要区别在于当前使用的数据是否在不同环境是一样的;
主要问题集中在id的引用。
3)可控性
- 用例不会无限执行下去;
重灾区是循环用例。 - 用例响应时间(用例偶尔执行失败)
主要针对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的情况。导致用例偶尔执行失败。 - 对于复杂逻辑,统一处理
比如循环请求一个http接口,需要注意while条件。但是这个很容易出错,每个人的水平也不一样,不如编写一个轮询http插件进行处理,简化流程。
4)可校验
- 所有的操作应该都是可校验的。
我看有一下接口只请求了连200校验都没有。
2,自动化测试场景
- 测试时间长,但主要是机械重复动作的测试;
- 需求变更频繁且每次变更影响面大;
- 测试需要大量回归测试;
除了使用自动化测试用例,也要标记一些重要用例进行人工测试。
3,常见语法使用
1)用例准备
1>函数助手
Ctrl+Shift+F1
常用函数:
| 说明 | 函数 |
|---|---|
| 当前日期 | ${__timeShift(yyyyMMdd,,,,)} |
| 昨天日期 | ${__timeShift(yyyyMMdd,,-P1D,,)} |
2>用户自定义变量
- 定义一次,全局享用
- 通过函数获取值
| 名称 | 值 |
|---|---|
| time | ${__time(,)} |
- 通过提前声明控制变量类型
如果在beanShell里之间vars.put(),这个变量会是字符串。
| 名称 | 值 |
|---|---|
| times | 0 |
- 在beanShell中操作
注意对象在vars中是以String的形式存放的。
String str = vars.get("time")
vars.put("time", str)
- 自定义插件中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请求默认值
- 全局生效
- 主要用于定义统一的http请求协议、ip、端口、编码格式

- 如果定义了utf-8还乱码,可能是http对应的jar包有问题
替换\lib\ext\ApacheJMeter_http.jar
4>HTTP信息管理器
| 名称 | 值 | 说明 |
|---|---|---|
| Accept-Language | zh-CN,zh;q=0.9,en;q=0.8 | |
| Content-Type | application/json;charset=UTF-8 | |
| Accept-Encoding | gzip, deflate, br | |
| User-Agent | Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36 | 浏览器 |
| accept | application/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取样器
注意:
-
文件上传类的接口:

-
url参数
可以直接在参数列表添加,也可以直接写url中。直接通过${}引用变量。
5)JDBC Request
数据库请求取样器,使用前需要先配置JDBC Connection Configuration。

6)响应断言
相比起来json断言可能比响应文本断言更好用。
7)json断言
通过jsonPath语法进行取值。


8)BeanShell 取样器
程序员的天下了。
- 取变量通过
vars变量才能获取,不能直接使用${} - 没有泛型
- 可以使用方法
- 外部引用需要import包
- 调试方法:log、或者下载源码通过IDEA运行本地调试。
9)BeanShell 断言
- 在BeanShell 取样器的基础上,增加了断言返回值的重写。
- 断言之后可以直接
return;,否则断言会继续往后走
//表示断言失败。
Failure = true;
//错误提示
FailureMessage = 数据库查询与返回结果不一致
10)前置后置执行顺序
- 配置元件(Config Element)
- 前置处理器(Pre Processors)
- 定时器(Timer)
- 取样器(Sampler)
- 后置处理器(Post Processors)
- 断言(Assertions)
- 监听器(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配置远程调试

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

被折叠的 条评论
为什么被折叠?



