为什么选择使用JMeter
当被问到这个问题的时候,也许你会在脑海里产生很多的理由,比如:
- Apache基金会下的开源项目,没有版权问题;
- 为数不多的还在持续更新的开源性能自动化测试工具;
- 支持协议丰富,是商用测试工具最佳替代品;
- 有专门的插件项目做支撑,使得你在实践中有更多的选择,比如http://jmeter-plugins.org/就提供了很多优秀的插件为你在使用JMeter执行测试时,可以选择更多的组件来制定测试计划,完成测试过程,监控测试数据。
而我的回答是:
关键在于不要简单地把JMeter理解为一个单纯的性能测试工具,而应该意识到它还是一个优秀的框架,这甚至成为我选择它的一个最根本理由,在这里所有的组件都可以通过自由编写插件的方式进行添加和完善,对于一个测试工程师来说为JMeter编写插件式组件其乐无穷!
JMeter基本组件类型及实现方法
对于JMeter的基本组件,我们可以将其简单的划分为两大类:
- 一类是具备GUI的组件,即可以通过JMeter图形管理控制器在测试计划Tree中进行添加的组件,主要包括ThreadGroup(线程组)、Config(配置元件)、Timer(定时器)、Modifier(前置处理器)、Extractor(后置处理器)、Controller(逻辑控制器)、Sampler(测试抽样器)、Assertion(断言)和Listener(监听器);
- 另一类是非GUI组件,这类组件典型的代表是Function(函数)和某些子测试抽样器,如JavaSamplerClient。
对于组件一般有两种实现方法:
- GUI与逻辑控制分离:GUI部分通过继承各种组件GUI抽象类,逻辑控制部分通过继承组件逻辑抽象类和实现各种接口方式从而实现不同组件的内部逻辑控制;
- GUI与逻辑控制不分离:与分离方法的区别在于不单独实现GUI部分,在逻辑控制部分通过实现TestBean接口方法从而实现对GUI界面的配置。
JMeter插件式组件实现细节概述
TestElement是所有组件的最基本单元,组件类都是TestElement类的子类,JMeter定义了上一章节所介绍的几种组件模型,并对其规范了各自所需发挥的作用。
(1)GUI部分的实现
GUI部分的实现我们可以在JMeter实现主类org.apache.jmeter.JMeter中发现端倪,该类实际实现了JMeterPlugin接口中的getIconMappings()方法来映射组件所对应的GUI图标,并为映射关系定义了一个二维数组,如下代码:
private static final String[][] DEFAULT_ICONS = {
{ "org.apache.jmeter.control.gui.TestPlanGui", "org/apache/jmeter/images/beaker.gif" },
{ "org.apache.jmeter.timers.gui.AbstractTimerGui", "org/apache/jmeter/images/timer.gif" },
{ "org.apache.jmeter.threads.gui.ThreadGroupGui", "org/apache/jmeter/images/thread.gif" },
{ "org.apache.jmeter.visualizers.gui.AbstractListenerGui", "org/apache/jmeter/images/meter.png" },
{ "org.apache.jmeter.config.gui.AbstractConfigGui", "org/apache/jmeter/images/testtubes.png" },
{ "org.apache.jmeter.processor.gui.AbstractPreProcessorGui", "org/apache/jmeter/images/leafnode.gif"},
{ "org.apache.jmeter.processor.gui.AbstractPostProcessorGui", "org/apache/jmeter/images/leafnodeflip.gif"},
{ "org.apache.jmeter.control.gui.AbstractControllerGui", "org/apache/jmeter/images/knob.gif" },
{ "org.apache.jmeter.control.gui.WorkBenchGui", "org/apache/jmeter/images/clipboard.gif" },
{ "org.apache.jmeter.samplers.gui.AbstractSamplerGui", "org/apache/jmeter/images/pipet.png" },
{ "org.apache.jmeter.assertions.gui.AbstractAssertionGui", "org/apache/jmeter/images/question.gif"}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
可以看到只要是插件类的GUI部分继承了以上数组中的GUI类,JMeter框架便会自动将其映射为所对应的组件类型和图标。
(2)逻辑控制部分的实现细节,我们根据组件类别进行一一介绍:
ThreadGroup(线程组)组件
ThreadGroup(线程组)组件继承AbstractThreadGroup抽象类,通过重写各类控制方法,如void scheduleThread(JMeterThread thread) 、stopThread(String threadName, boolean now) 、threadFinished(JMeterThread thread)等,来达到控制和协调各线程(虚拟用户)的行为,线程组是构建一个性能测试模型的最基本组件。
Config(配置元件)组件
Config(配置元件)组件相对其他组件比较特殊,通过继承ConfigTestElement类或只需要GUI部分的实现即可完成本体任务,而对于一个需要配置的组件类则需要实现ConfigMergabilityIndicator接口的public boolean applies(ConfigTestElement configElement)方法,用来指明哪些Config组件可以用来对其进行配置,这里参考TCPSampler的源代码如下:
private static final Set<String> APPLIABLE_CONFIG_CLASSES = new HashSet<String>(
Arrays.asList(new String[]{
"org.apache.jmeter.config.gui.LoginConfigGui",
"org.apache.jmeter.protocol.tcp.config.gui.TCPConfigGui",
"org.apache.jmeter.config.gui.SimpleConfigGui"}));
@Override
public boolean applies(ConfigTestElement configElement) {
String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue();
return APPLIABLE_CONFIG_CLASSES.contains(guiClass);
}
以上代码指明LoginConfigGui、SimpleConfigGui和TCPConfigGui这三个配置元件可以对TCPSampler组件进行配置。
Timer(定时器)组件
Timer(定时器)组件通过继承AbstractTestElement抽象类,实现Timer接口的delay()方法来实现对时间的控制,主要的控制内容如下:
- 控制线程延时,即用来模仿思考时间(ThinkTime)或键盘时间(KeyTime);
- 控制线程行为,如SyncTimer(同步计时器),就是内部利用CyclicBarrier来控制阻塞和释放全部运行线程的逻辑行为,从而达到“集合点”的目的。
Modifier(前置处理器)组件
Modifier(前置处理器)组件通过继承AbstractTestElement抽象类,实现PreProcessor接口的process ()方法控制逻辑,常常需要对线程上下文中的当前Sampler和前一个SampleResult进行识别和判断,以做出正确的处理,一般的行为是通过取出SampleResult的某些值或直接在当前Sampler启动sample方法之前对其某些属性进行修饰。
Extractor(后置处理器)组件
Extractor(后置处理器)组件通过继承AbstractTestElement抽象类,实现PostProcessor接口的process ()方法控制逻辑,常常需要对线程上下文中的前一个SampleResult进行识别和判断,以做出正确的处理。
Controller(控制器)组件
Controller(控制器)组件通过继承GenericController类,通过重写Sampler next()、void setDone(boolean done)、int getIterCount()、void reInitialize()等方法来控制Sampler的测试行为。
Sampler(测试抽样器)组件
Sampler(测试抽样器)组件继承AbstractSampler抽象类,通过重写SampleResult sample(Entry e)方法,实现测试过程以及测试结果的采集功能。
Assertion(断言)组件
Assertion(断言)组件通过继承AbstractTestElement抽象类,实现Assertion接口的getResult(SampleResult result)方法对结果内容进行判断,从而实现断言方法,用于对Sampler组件所产生的抽样采集结果内容进行断言。
Listener(监听器)主要有两种方案:
- 直接继承AbstractTestElement,实现sampleListener或Visualizer等接口方法
- 实现ResultCollector和Runnable等接口方法
我们可以从实际用途上将其分为两大类Report (报告)和Vizualizers(监视器)。
Report (报告)继承AbstractListenerElement抽象类,通过实现sampleOccurred(SampleEvent e)方法,对所有采集事件中所产生的SampleResult进行处理,从而生成报告;
Vizualizers(监视器)主要用于特定的监控任务,比如监控系统资源利用率的组件,与Report的区别在于Vizualizers必须继承一个 ResultCollector类,并在收集器中通过开启额外线程方式完成自定义的数据采集。
上面还介绍了诸如Function(函数)这一类非GUI组件,这类组件的实现比较简单,而且功能比较单一,只需要继承相应的抽象类。
(3)一些TestElement需要实现的主要接口说明
为了实现更多的特性,组件在必要时还需要实现一些主要的接口和方法,下面举例说明:
NoThreadClone接口:
This class is not cloned per thread, so this is shared,可以理解为一旦实现了NoThreadClone接口,这个TestElement便不会在线程组下的每个线程中创建,而是一个全局化的组件,因此,无法使用getThreadContext()方法。反之,我们可以发现JMeter实际是通过TestElement的clone()方法为线程组下的每个线程拷贝创建属于各自的线程上下文内的TestElement,A new instance is created for each thread group, and the clone() method is then called to create copies for each thread in a thread group.如果想要对某些共享资源进行同步操作,需要参考如下方法:
private transient Object lock = new Object();//锁对象不需要进行序列化,因为分布式在不同主机内存中的锁对象不必保持一致
@Override
public Object clone() {
Clazz clazz = (Clazz) super.clone();
clazz.lock = lock; //保证所有克隆对象共享同一个锁对象
return clazz;
}
注:这是种显式的共享对象方法,由于Java在克隆类对象时,默认是一种“浅克隆”方式,因此,不显式的共享上述锁对象,该锁对象也是默认共享的。
LoopIterationListener接口:
将通过实现 iterationStart(LoopIterationEvent event)方法,控制对每次发生迭代事件时所需要实现的逻辑。
Serializable接口:
为了实现在分布式测试模型中保持一些配置和对象的一致性,就需要实现Serializable接口,通过序列化方式保持不同主机对象属性的一致性。
TestStateListener接口:
将通过实现testStarted(String string)和testEnded(String string)方法,控制对测试状态变化事件时所需要实现的逻辑。
另外,在实际插件的编写过程中还会包括如Remoteable、Interruptible、ThreadListener等接口的应用,会在后面的具体章节进行详细介绍和应用。
组件即插件,只需一步插入JMeter框架
将编写好的插件式组件插入JMeter框架非常简单,只需要将组件整体打包为jar包,并将其拷贝到$JMETER_HOME/ lib/ext路径下即可使用!