一. 引言:
最近工作中的一个项目要求做性能测试,该项目由提供服务的几个应用组成,选用的框架是阿里巴巴公司开源的服务框架Dubbo。关于Dubbo的介绍,网上也有很多资料,本人只是做了粗略的了解,没有深入研究,相关资料地址如下:http://www.iteye.com/magazines/103,http://alibaba.github.io/dubbo-doc-static/User+Guide-zh.htm#UserGuide-zh-%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95%E6%8A%A5%E5%91%8A,http://www.oschina.net/p/dubbo, 应用实例(含源码):http://blog.csdn.net/wilsonke/article/details/39896595,http://download.csdn.net/detail/u012049463/6338227
Jmeter(官网:http://jmeter.apache.org/download_jmeter.cgi)做为一款开源的性能测试工具,提供了详细的用户操作手册、demo以及api文档,一直都是我们做性能测试的首选,不过之前大多做的都是Http请求的测试,对接口形式的Java请求测试并没有实践过。最快的学习方法就是从网上找例子,依葫芦画瓢就成,的确我也是这么干的,虽然网上类似的帖子也很多,不过这里还是想对自己的实践过程做个记录。^^
二. 正文:
1. 怎么写Jmeter接口测试的java测试类
其实,Jmeter已经给我们提供了一个java sampler测试用例类的框架了,在api文档(软件包的apache-jmeter-2.11/docs/api/index.html路径下)中我们可以看到org.apache.jmeter.protocol.java.sampler包中有一个接口JavaSamplerClient和一个抽象类AbstractJavaSamplerClient,我们的测试用例类只要实现它提供的接口或者直接继承这个抽象类就可以了。
JavaSamplerClient接口定义了四个方法:
getDefaultParameters() 用于获取界面的参数;runTest(JavaSamplerContext context) 用于写具体的测试用例,类似于LR的Action,在抽象类AbstractJavaSamplerClient并没有写该方法的实现,测试用例当然是要我们自己去写实现了;setupTest(JavaSamplerContext context) 初始化方法,类似于LR的init和Junit中的setUp();teardownTest(JavaSamplerContext context) 类似于LR的end和Junit中的tearDown()。
费了这么多话,还是赶紧用上面网上的例子(http://download.csdn.net/detail/u012049463/6338227)来说明下吧,新建一个Java项目(最好是maven项目,这样打包方便),引入Jmeter中lib\ext基础包:ApacheJMeter_java.jar、ApacheJMeter_core.jar就可以开始编写测试类了。
本例中,服务端提供的服务接口和具体实现类
package com.huangjie.dubbo_Service.service;
public interface IProcessData {
public String setAge(String age); public String sayHi(String name); }
package com.huangjie.dubbo_Service.service.impl;
import com.huangjie.dubbo_Service.service.IProcessData;
public class ProcessDataImpl implements IProcessData { /* * @see com.xxx.bubbo.provider.IProcessData#deal(java.lang.String) */ @Override public String setAge(String age) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "You are " + age + "years old!"; } @Override public String sayHi(String name) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "Hi:" + name; } }
客户端JavaSampler测试用例类
package com.huangjie.dubbo_Consumer;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient; import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; import org.apache.jmeter.samplers.SampleResult; import org.apache.log.Logger; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.huangjie.dubbo_Service.service.IProcessData; public class JmeterInterfaceTest extends AbstractJavaSamplerClient{ private static String label_name = "dubbo_consumer";//定义label名称,显示在jmeter的结果窗口 private static final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath*:applicationConsumer.xml"); private static IProcessData demoService = null; private Logger log = getLogger(); @Override public void setupTest(JavaSamplerContext arg0) { log.info("测试用例开始执行..."); //定义测试初始值,setupTest只在测试开始前使用 demoService = (IProcessData) context.getBean("demoService"); } @Override public Arguments getDefaultParameters() { //参数定义,显示在前台,也可以不定义 Arguments params = new Arguments(); params.addArgument("name", "Tom"); params.addArgument("age", "23"); return params; } @Override public SampleResult runTest(JavaSamplerContext arg0) { boolean success = true; SampleResult sr = new SampleResult(); sr.setSampleLabel(label_name); sr.sampleStart();//用来统计执行时间--start-- try { String name = arg0.getParameter("name"); String msg = demoService.sayHi(name); Thread.sleep(5000); System.out.println(msg); sr.setResponseMessage(msg); sr.setResponseCode("1000"); } catch (Exception e) { success = false; }finally{ sr.sampleEnd();//用来统计执行时间--end-- sr.setSuccessful(success); } return sr; } @Override public void teardownTest(JavaSamplerContext arg0){ log.info("测试用例执行结束..."); } }
说明:
首先,getDefaultParameters()方法是用来定义参数的,这里我们定义了name和age两个参数并赋了默认值”Tom”和“23”,这样在jmeter创建的java请求中选择上面的测试类就能看到这两个参数了,并且参数值也是可以任意修改的。
接着,仔细看会发现,测试类里面我们只调用了sayHi()这个方法,并没有调用服务端提供的setAge()方法,之所以不在一个测试用例里面去调用两个测试方法是因为我们这里的目的是去测试单一的场景,也就是如果要分别测这两个方法那就得再建一个测试用例类,其它部分的代码都可以不变,只要把实现方法替换一下就可以了。因此,我们会想,如果一个接口提供n种方法,那么我们就要写类似的n个类,虽说复制粘贴很容易,但这样就会产生一堆类文件,感觉很冗余,这里可以用Java的反射机制来实现方法的动态调用。简单的说就是,在runTest()方法中不写死具体用例调用的方法,而是由Java反射机制通过输入参数来决定。改写上面的测试用例类如下:
先写一个抽象类来继承AbstractJavaSamplerClient,里面只写动态调用的方法,其它方法还是在用例类里面重写。
package com.huangjie.dubbo_Consumer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient; import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; import org.apache.jmeter.samplers.SampleResult; public abstract class AbstractServiceClient extends AbstractJavaSamplerClient{ public Object invokeRunTest(String testName, JavaSamplerContext context , SampleResult sampleResult){ Method[] methods = this.getClass().getMethods(); for (Method method : methods) { if (method.getName().equalsIgnoreCase(testName)) { try { return method.invoke(this, context, sampleResult); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } return null; } }
测试用例类继承上面的动态方法调用的抽象类
package com.huangjie.dubbo_Consumer;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; import org.apache.jmeter.samplers.SampleResult; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.huangjie.dubbo_Service.service.IProcessData; public class DynamicMethodInvokeTest extends AbstractServiceClient{ private static String label_name = "dubbo_consumer";//定义label名称,显示在jmeter的结果窗口 private static final ClassPathXmlApplicationContext cxt = new ClassPathXmlApplicationContext("classpath*:applicationConsumer.xml"); private static IProcessData demoService = null; @Override public void setupTest(JavaSamplerContext context) { //定义测试初始值,setupTest只在测试开始前使用 super.setupTest(context); demoService = (IProcessData) cxt.getBean("demoService"); } @Override public Arguments getDefaultParameters() { //参数定义,显示在前台,也可以不定义 Arguments params = new Arguments(); params.addArgument("name", "Tom"); params.addArgument("age", "20"); params.addArgument("testName", "setAge"); return params; } @Override public SampleResult runTest(JavaSamplerContext context) { String testName = context.getParameter("testName"); SampleResult sr = new SampleResult(); super.invokeRunTest(testName, context, sr); return sr; } public void setAge(JavaSamplerContext context, SampleResult sr){ boolean success = true; sr.setSampleLabel("setAge"); sr.sampleStart(); try { String age = context.getParameter("age"); String msg = demoService.setAge(age); Thread.sleep(5000); System.out.println(msg); sr.setResponseMessage(msg); sr.setResponseCode("1000"); } catch (Exception e) { success = false; }finally{ sr.sampleEnd(); sr.setSuccessful(success); } } public void sayHi(JavaSamplerContext context, SampleResult sr){ boolean success = true; sr.setSampleLabel("sayHi"); sr.sampleStart(); try { String name = context.getParameter("name"); String msg = demoService.sayHi("Hi,"+name); Thread.sleep(5000); System.out.println(msg); sr.setResponseMessage(msg); sr.setResponseCode("1001"); } catch (Exception e) { success = false; }finally{ sr.sampleEnd(); sr.setSuccessful(success); } } @Override public void teardownTest(JavaSamplerContext context){ super.teardownTest(context); } }
这里我们新增了一个参数testName,通过这个参数来决定具体调用哪个用例方法。这样,我们就实现了在同一个类里面写不同的测试用例了,感觉优雅多了,哈哈!
二. Jmeter中创建Java Sampler Test Plan
想要在Jmeter中执行上面写的测试用例,有以下几个步骤:
1. 把项目打成jar包,方法1用eclipse或者myeclipse的export直接导出jar包,方法2用maven的package或这install指令打包,这里建议用maven来管理项目,打包方便并且不会出错,我试过用IDE直接export出jar包,但执行的过程中报找不到spring的bean配置文件,用maven打包没有出现这种情况。
2.将打好的jar包放到Jmeter的lib/ext路径下,运行Jmeter,在新建的Java请求Sampler中就能找到自己写的测试用例类了。
3.把项目依赖的所有jar包拷贝到Jmeter的lib路径下,因为跑自己的测试用例可能用到这些jar中的类,否则执行过程中会报找不到class。如果是maven项目,则可以通过执行指令mvn dependency:copy-dependencies 把项目中所有依赖的jar导出到项目的target/dependency文件夹中,然后拷贝到Jmeter的lib下。
4.运行Jmeter按照具体的性能指标和场景,开始创建你的测试计划吧。
附:demo源码 http://pan.baidu.com/s/1qWNV65Q