一、Jmeter的簡介
Jmeter一款開源的壓力測試工具幕帆,而這款開源的測試工具是基于Java開發(fā)眯搭。Jmeter最初的設(shè)計(jì)是為了web的性能測試。而在后面擴(kuò)展了很多種類的測試恭金。
Jmeter是基于Java編寫,所以使用時(shí)需要安裝jdk
二褂策、Jmeter負(fù)載測試和性能測試種類
1.Web - HTTP, HTTPS (Java, NodeJS, PHP, ASP.NET, …)
2.SOAP / REST Webservices
3.FTP
4.Database via JDBC
5.LDAP
6.Message-oriented middleware (MOM) via JMS
7.Mail - SMTP(S), POP3(S) and IMAP(S)
8.Native commands or shell scripts
9.TCP
10.Java Objects
三横腿、Jmeter基本組件簡介
我們這里只講解使用到的一些組件。而其他組件可以到Jmeter的官網(wǎng)了解(https://jmeter.apache.org/)斤寂,在Jmeter下每個(gè)組件都是節(jié)點(diǎn)的方式進(jìn)行配置耿焊。如我們在圖形化界面中,都會有一個(gè)TestPlan的根節(jié)點(diǎn)扬蕊,其他控件都添加在根節(jié)點(diǎn)下搀别。
3.1.TestPlan
測試計(jì)劃,每一個(gè)測試都為一個(gè)測試計(jì)劃尾抑。
2.ThreadGroup:是一個(gè)測試計(jì)劃的開始歇父。所有的controller蒂培、sampler必須在線程組下。不過有一些特許的控件如Listeners可以直接在TestPlan下榜苫。
3.sampler:采樣器护戳,也就是我們各種性能測試和負(fù)載測試的收集器。如:http采樣器:HTTPSampler等
4.Controller:主要用于壓力測試邏輯的處理垂睬,如我們這里使用了LoopController進(jìn)行控制線程的循環(huán)次數(shù)媳荒,是永久還是循環(huán)壓力測試多次。
四驹饺、Jmeter的調(diào)用方式
調(diào)用Jmeter有5中方式:
- 使用命令的方式
- 使用ANT的方式
- 使用MAVEN的PLUGIN方式
- 使用JAVA代碼調(diào)用
- 使用blazemeter進(jìn)行調(diào)用
這里只演示JAVA調(diào)用钳枕,其他方式的調(diào)用可以blazemeter的文章(https://www.blazemeter.com/blog/5-ways-launch-jmeter-test-without-using-jmeter-gui)
五、使用JAVA調(diào)用jmeter
一赏壹、創(chuàng)建項(xiàng)目
我們這里使用了Ecplise IDE創(chuàng)建Maven項(xiàng)目鱼炒。
二、導(dǎo)入Jmeter的包
我們這里演示使用的是http的壓力測試蝌借。所以會用到ApacheJMeter_http的包和ApacheJMeter_core的包
<!--jmeter核心包-->
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_core</artifactId>
<version>4.0</version>
</dependency>
<!--jmeter組件包-->
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_components</artifactId>
<version>4.0</version>
</dependency>
<!--jmeter Http包-->
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_http</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
三昔瞧、演示代碼
package com.study;
import java.io.File;
import org.apache.jmeter.JMeter;
import org.apache.jmeter.control.LoopController;
import org.apache.jmeter.engine.StandardJMeterEngine;
import org.apache.jmeter.protocol.http.control.HeaderManager;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
import org.apache.jmeter.reporters.ResultCollector;
import org.apache.jmeter.reporters.Summariser;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestPlan;
import org.apache.jmeter.threads.ThreadGroup;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.collections.HashTree;
public class TestPlanLauncher {
public static void main(String[] args) {
// jemter 引擎
StandardJMeterEngine standardJMeterEngine = new StandardJMeterEngine();
// 設(shè)置不適用gui的方式調(diào)用jmeter
System.setProperty(JMeter.JMETER_NON_GUI, "true");
// 設(shè)置jmeter.properties文件,我們將jmeter文件存放在resources中菩佑,通過classload
String path = TestPlanLauncher.class.getClassLoader().getResource("jmeter.properties").getPath();
File jmeterPropertiesFile = new File(path);
if (jmeterPropertiesFile.exists()) {
JMeterUtils.loadJMeterProperties(jmeterPropertiesFile.getPath());
HashTree testPlanTree = new HashTree();
// 創(chuàng)建測試計(jì)劃
TestPlan testPlan = new TestPlan("Create JMeter Script From Java Code");
// 創(chuàng)建http請求收集器
HTTPSamplerProxy examplecomSampler = createHTTPSamplerProxy();
// 創(chuàng)建循環(huán)控制器
LoopController loopController = createLoopController();
// 創(chuàng)建線程組
ThreadGroup threadGroup = createThreadGroup();
// 線程組設(shè)置循環(huán)控制
threadGroup.setSamplerController(loopController);
// 將測試計(jì)劃添加到測試配置樹種
HashTree threadGroupHashTree = testPlanTree.add(testPlan, threadGroup);
// 將http請求采樣器添加到線程組下
threadGroupHashTree.add(examplecomSampler);
//增加結(jié)果收集
Summariser summer = null;
String summariserName = JMeterUtils.getPropDefault("summariser.name", "summary");
if (summariserName.length() > 0) {
summer = new Summariser(summariserName);
}
ResultCollector logger = new ResultCollector(summer);
testPlanTree.add(testPlanTree.getArray(), logger);
// 配置jmeter
standardJMeterEngine.configure(testPlanTree);
// 運(yùn)行
standardJMeterEngine.run();
}
}
/**
* 創(chuàng)建線程組
*
* @return
*/
public static ThreadGroup createThreadGroup() {
ThreadGroup threadGroup = new ThreadGroup();
threadGroup.setName("Example Thread Group");
threadGroup.setNumThreads(1);
threadGroup.setRampUp(0);
threadGroup.setProperty(TestElement.TEST_CLASS, ThreadGroup.class.getName());
threadGroup.setScheduler(true);
threadGroup.setDuration(60);
threadGroup.setDelay(0);
return threadGroup;
}
/**
* 創(chuàng)建循環(huán)控制器
*
* @return
*/
public static LoopController createLoopController() {
// Loop Controller
LoopController loopController = new LoopController();
loopController.setLoops(-1);
loopController.setContinueForever(true);
loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName());
loopController.initialize();
return loopController;
}
/**
* 創(chuàng)建http采樣器
*
* @return
*/
public static HTTPSamplerProxy createHTTPSamplerProxy() {
HeaderManager headerManager = new HeaderManager();
headerManager.setProperty("Content-Type", "multipart/form-data");
HTTPSamplerProxy httpSamplerProxy = new HTTPSamplerProxy();
httpSamplerProxy.setDomain("www.baidu.com");
httpSamplerProxy.setPort(80);
httpSamplerProxy.setPath("/");
httpSamplerProxy.setMethod("GET");
httpSamplerProxy.setConnectTimeout("5000");
httpSamplerProxy.setUseKeepAlive(true);
httpSamplerProxy.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName());
httpSamplerProxy.setHeaderManager(headerManager);
return httpSamplerProxy;
}
}
三自晰、詳講
3.1 Jmeter引擎StandardJMeterEngine
StandardJMeterEngine是Java調(diào)用Jmeter的入口。
3.2 Jmeter.properties文件
- 指定Jmeter.properties文件稍坯,Jmeter.properties文件主要用于Jmeter全局基礎(chǔ)配置酬荞。我們可以根據(jù)自己的需求進(jìn)行修改配置文件。
例如我們需要進(jìn)行分布式壓力測試的情況下劣光,我們就需要在remote_hosts添加遠(yuǎn)程的IP袜蚕。 - 指定jmeter.properties文件的方式糟把,我們可以通過源碼的分析發(fā)現(xiàn)加載配置Jmeter.properties配置文件绢涡,先從指定的文件中查找,如果查找不到j(luò)meter.properties文件遣疯,會在org/apache/jmeter/jmeter.properties查找查找雄可。也就是說我們可以將配置文件存放在org/apache/jmeter路徑下或者指定配置文件。
public static void loadJMeterProperties(String file) {
Properties p = new Properties(System.getProperties());
InputStream is = null;
try {
File f = new File(file);
is = new FileInputStream(f);
p.load(is);
} catch (IOException e) {
try {
is = ClassLoader.getSystemResourceAsStream(
"org/apache/jmeter/jmeter.properties"); // $NON-NLS-1$
if (is == null) {
throw new RuntimeException("Could not read JMeter properties file:" + file);
}
p.load(is);
} catch (IOException ex) {
throw new RuntimeException("Could not read JMeter properties file:" + file);
}
} finally {
JOrphanUtils.closeQuietly(is);
}
appProperties = p;
}
3.3 HashTree
我們在使用Jmeter的圖形化界面的時(shí)候缠犀,我們可以看出所有組件都是添加在TestPlan下数苫。而在使用非圖形化界面的時(shí),我們的StandardJMeterEngine通過configure方法來進(jìn)行配置我們的TestPlan辨液、ThreadGroup虐急、LoopController等組件,因此需要創(chuàng)建HashTree滔迈。
3.4 TestPlan
我們在使用圖形化界面都知道所有組件都是存放在TestPlan中止吁,所以我們需要創(chuàng)建一個(gè)TestPlan用于存放組件被辑。
3.5 HTTPSamplerProxy
HTTPSamplerProxy是HTTP采樣器,在Jmeter中提供做了多種采樣器敬惦,我們可以根據(jù)采樣數(shù)據(jù)來確定到底使用哪一個(gè)采樣器盼理。在壓力測試時(shí),是通過Thread Group來進(jìn)行創(chuàng)建線程進(jìn)行壓力測試的俄删。每個(gè)線程都需要通過Sampler來確定去并發(fā)訪問宏怔,因此Sampler是存放在Thread Group下。
3.6 LoopController
LoopController是一個(gè)壓力循環(huán)次數(shù)的控制器畴椰。我們通常會配置ThreadGroup進(jìn)行使用臊诊,例如我們在Thread Group中設(shè)置持續(xù)5秒,并發(fā)量50的情況下斜脂,我們會這是LoopController為永久循環(huán)妨猩。我們需要注意:永久循環(huán)的情況下loops應(yīng)設(shè)置為-1.
loopController.setLoops(-1);
loopController.setContinueForever(true);
注意:Controller是存放在Thread Group下。
3.7 ThreadGroup
在Jmeter下是通過線程方式去并發(fā)訪問秽褒,線程管理Jmeter通過ThreadGroup來控制線程的數(shù)量和線程的創(chuàng)建壶硅、線程持續(xù)訪問的時(shí)間。
3.8 ResultCollector
- ResultCollector是一個(gè)結(jié)果的收集器销斟,ResultController收集請求結(jié)果是通過監(jiān)聽的方式進(jìn)行收集結(jié)果庐椒。
- 通過查看ResultController的源碼發(fā)現(xiàn)其實(shí)現(xiàn)了SampleListener接口,并且調(diào)用sampleOccurred方法處理每個(gè)sampler的結(jié)果蚂踊。
/**
* When a test result is received, display it and save it.
*
* @param event
* the sample event that was received
*/
@Override
public void sampleOccurred(SampleEvent event) {
SampleResult result = event.getResult();
if (isSampleWanted(result.isSuccessful())) {
sendToVisualizer(result);
if (out != null && !isResultMarked(result) && !this.isStats) {
SampleSaveConfiguration config = getSaveConfig();
result.setSaveConfig(config);
try {
if (config.saveAsXml()) {
SaveService.saveSampleResult(event, out);
} else { // !saveAsXml
String savee = CSVSaveService.resultToDelimitedString(event);
out.println(savee);
}
} catch (Exception err) {
log.error("Error trying to record a sample", err); // should throw exception back to caller
}
}
}
if(summariser != null) {
summariser.sampleOccurred(event);
}
}
- 在ResultController.sampleOccurred方法中我們可以看到summariser不為空的情況下調(diào)用summariser的sampleOccurred方法约谈,從summariser.sampleOccurred方法我們可以知道summariser一定是實(shí)現(xiàn)了SampleListener接口。
public class Summariser extends AbstractTestElement
implements Serializable, SampleListener, TestStateListener, NoThreadClone, Remoteable {
- 在多線程的情況下犁钟,Jmeter會對Controller棱诱、Sampler等組件為每個(gè)線程拷貝一份對象。而對于組件ResultCollector是不拷貝對象涝动。
/**
* This class handles all saving of samples.
* The class must be thread-safe because it is shared between threads (NoThreadClone).
*/
public class ResultCollector extends AbstractListenerElement implements SampleListener, Clearable, Serializable,
TestStateListener, Remoteable, NoThreadClone {
Reference:
https://jmeter.apache.org/api/index.html
https://www.blazemeter.com/blog/5-ways-launch-jmeter-test-without-using-jmeter-gui
https://jmeter.apache.org/usermanual/get-started.html