Programming with JMeter

阅读更多

 

习惯于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_第1张图片
      从xml tree看,对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$

 

 

你可能感兴趣的:(Programming with JMeter)