欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Programming with JMeter

程序员文章站 2022-03-02 11:52:36
...

 

习惯于JUnit做功能方面unit test,而对于有些Test需要有一定的压力来模拟一定并发的读和写,借助JMeter来实现这样的测试框架是很不错的一个选择,一来减少很多工作量 (只需少量的定制:比如实现自己的ThreadGroup来定制并发线程的创建和执行,实现自己的Sampler来定制测试目标类的实例化和运行),二来可以很方便使用Hudson进行持续集成, 这对于利用Hudson进行持续集成的项目是再方便不过了,每次build之后除了JUnit报告之外,还能看到jtl报告来监视的性能状况。如果要这样做,首先要研究一下JMeter代码。

咋看JMeter框架,貌似挺复杂的,其实不然,基于上面的需求,我们只需要Java Sampler的测试,所以大部分的Jar包就可以排除,只需区区10个JAR包:

commons-logging-1.1.1.jar

xstream-1.4.2.jar

commons-lang3-3.1.jar

ApacheJMeter_java.jar

xmlpull-1.1.3.1.jar

logkit-2.0.jar

commons-io-2.2.jar

avalon-framework-4.1.4.jar

ApacheJMeter_core.jar

jorphan.jar

     好吧,要研究10个JAR包的代码也不算少!幸运的是,剔除commons/xml/log相关的,真正需要研究的只有ApacheJMeter_core.jar/ApacheJMeter_java.jar,最多再了解下jorphan.jar。

首先,从JMeter的headless运行入手:$ jmeter -n -p user.properties -t my_test_plan.jmx -l my_results.jtl

其实就是调用 NewDriver.main("-n -p user.properties -t my_test_plan.jmx -l my_results.jtl").NewDriver类位于ApacheJMeter.jar中,之所以该jar没有列入上面10个之中是因为这个jar 包非常简单,只是一个headless运行的入口而已,真正项目要集成JMeter跟本不会去用这个类。作为CommandLine运行入 口,NewDriver只做两件事情,一是设置一些JMeter路径(主要是为了程序能找到 user.properties,saveservice.properties,和相关JAR的classpath),二是去反射运行 JMeter.start方法。 

再来看位于ApacheJMeter_core.jar中JMeter类,抛开GUI和Remote test相关的代码,简单说,JMeter做的事情主要有

1. 解析命令行参数,加载user.properties(初始化log)

2. 查找并加载saveservice.properties

3. 将测试文件(.jmx文件)解析成HashTree

4. 创建一个StandardJMeterEngine,并把测试的工作交给JMeterEngine

当然,他还有其他重要的职责,比如监听所有的JMeterEngine,当接收到GUI的StopTestNow/Shutdown等命令时候来调用JMeterEngine相应的方法。

       接下来研究下JMeterEngine,很简单的一个接口:

 

public interface JMeterEngine {
    void configure(HashTree testPlan);

    void runTest() throws JMeterEngineException;

    void stopTest(boolean now);

    void reset();

    void setProperties(Properties p);

    void exit();
    
    boolean isActive();
}

         JMeterEngine只依赖HashTree, 只要能够构造一个HashTree, 就可以简单的调用其runTest方法完成所有的测试工作了. 而HashTree是由执行的测试文件如my_test_plan.jmx解析而来,其结构一般如下:

 


Programming with JMeter
            
    
    博客分类: JMeter  
      从xml tree看,对JMeter内部类应该有这样的直觉:


Programming with JMeter
            
    
    博客分类: JMeter  
 

       单纯从数据结构上可以这么理解,一个testplan包含多个ThreadGroup,每个ThreadGroup可以起一组线程跑相应JavaSampler测试,每个测试由相应的ResultCollector收集结果并生产report。其实并没有这么简单,这里有最重要的两个类可以进行扩展,实现和自己项目的集成,就是ThreadGroup和JavaSampler,这在本文开头就提过,至于如何扩展等分析完这些组件再写。本文接下来简单的以实例代码结束:

 1. 构建一个Mockup HashTree,这样就能摆脱以命令行跑需要提供一个JMX测试文件的依赖:

public static HashTree createMockHashTree() throws IOException{
		TestPlan tp = testPlan();
		ThreadGroup group = threadGroup();
		JavaSampler sampler = javaSampler();
		ResultCollector resultCol = resultCollector();
		
		HashTree tree = new HashTree();
		tree.add(tp);
		
		HashTree groupTree = new HashTree();
		groupTree.add(group);

		HashTree samplerTree = new HashTree();
		samplerTree.add(sampler);
		
		HashTree resultTree = new HashTree();
		resultTree.add(resultCol);
		
		
		samplerTree.add(samplerTree.getArray()[0], resultTree);
		groupTree.add(groupTree.getArray()[0],samplerTree);
		
		
		tree.add(tree.getArray()[0], groupTree);
		
        return tree;
	}

	public static TestPlan testPlan() throws IOException{
		TestPlan tp = new TestPlan();
		tp.setName("MyTest");
		tp.setProperty(TestElement.TEST_CLASS, "TestPlan");
		tp.setProperty(TestElement.GUI_CLASS,"TestPlanGui");
		tp.setProperty(TestElement.ENABLED, true);
		tp.setComment("");
		tp.setFunctionalMode(false);
		tp.setSerialized(false);
		return tp;
	}
	
	public static ThreadGroup threadGroup() throws IOException{
		ThreadGroup tp = new ThreadGroup();
		tp.setName("PerformanceGroup");
		tp.setProperty(TestElement.TEST_CLASS, "ThreadGroup");
		tp.setProperty(TestElement.GUI_CLASS,"ThreadGroupGui");
		tp.setProperty(TestElement.ENABLED, true);
		tp.setProperty(AbstractThreadGroup.ON_SAMPLE_ERROR, "continue");
		LoopController lc = new LoopController();
		lc.setName("Loop Controller");
		lc.setLoops(10);
		tp.setSamplerController(lc);
		tp.setNumThreads(5);
		tp.setRampUp(1);
		tp.setStartTime(System.currentTimeMillis());
		tp.setEndTime(System.currentTimeMillis());
		tp.setScheduler(false);
		return tp;
	}
	
	public static JavaSampler javaSampler() throws IOException{
		JavaSampler tp = new JavaSampler();
		tp.setName("PerformanceTest");
		tp.setProperty(TestElement.TEST_CLASS, "JavaSampler");
		tp.setProperty(TestElement.GUI_CLASS,"JavaTestSamplerGui");
		tp.setProperty(TestElement.ENABLED, true);
		tp.setClassname("app.PerformanceTest");
		return tp;
	}
	
	public static ResultCollector resultCollector() throws IOException{
		ResultCollector tp = new ResultCollector();
		tp.setName("Aggregate Report");
		tp.setProperty(TestElement.TEST_CLASS, "ResultCollector");
		tp.setProperty(TestElement.GUI_CLASS,"StatVisualizer");
		tp.setProperty(TestElement.ENABLED, true);
		tp.setErrorLogging(false);
		tp.setFilename("/home/wilson.wu/unittest/report/performentce2.jtl");
		return tp;
	}

 

  2. 有了HashTree, 最简单的做法:

StandardJMeterEngine jmeterEngine = new StandardJMeterEngine();
jmeterEngine .configure(tree);
jmeterEngine .runTest();

 

最后,要注意一点,在编程调用JMeter相关JAR之前,运行相关配置文件的设置,例如:

JMeterUtils.loadJMeterProperties(userProperties);
		JMeterUtils.initLogging(); 
		JMeterUtils.initLocale(); 
		JMeterUtils.setJMeterHome("");
		// Set some (hopefully!) useful properties
        long now=System.currentTimeMillis();
        JMeterUtils.setProperty("START.MS",Long.toString(now));// $NON-NLS-1$
        Date today=new Date(now); // so it agrees with above
        JMeterUtils.setProperty("START.YMD",new SimpleDateFormat("yyyyMMdd").format(today));// $NON-NLS-1$ $NON-NLS-2$
        JMeterUtils.setProperty("START.HMS",new SimpleDateFormat("HHmmss").format(today));// $NON-NLS-1$ $NON-NLS-2$