自定义接口测试脚本与jmeter测试脚本的转化

作者: zhaochenxi 分类: 测试 发布时间: 2016-12-08 21:30

一、测试工具
为了提高后端接口测试效率,我们考虑实现自动化测试平台提供给测试人员测试,开发自测,在平台中录入了大量的测试用例,通过定义测试任务,平台会自动执行用例,然后统计测试的结果,通过邮件将测试结果发送给案例的创建者,这样工具不但实现了测试用例的管理也实现了接口的测试功能。但是目前开源市场上有很多优秀接口测试工具,比如jmeter,很多测试同学已经习惯了使用这些工具,而且这些工具功能十分强大,我们的工具执行的也是测试用例,那么是否可以将我们的用例转化成jmeter的测试用例呢,通过研究我们发现是可以转化的。

二、jmeter脚本
熟悉jmeter测试的同学应该知道,jmeter的测试脚本中最基本的是在一个测试计划中包含了线程组,然后在线程组中添加测试用例和配置元件等,如下图是一个jmeter的测试计划。

其中的http请求就是我们需要执行的接口测试用例,请求下面还可以添加一些断言,后置处理器等工具,然后我们只要运行即可获得我们想要的结果,还可以添加更多的配置项来实现更加强大的功能。那么这个脚本是怎么存储的呢?jmeter是一个运行在本地的可执行程序,没有自己的数据库,它使用jmx文件来存储脚本信息。

我们打开jmx文件会发现,jmx文件其实就是一个xml文件。

jmeter将编辑的测试用例的信息全部通过xml文件来保存,然后改名成jmx,额,以为换个名字我们就不认识它了。这就意味着我们只需要将我们自己实现的接口测试工具中的测试用例按照jmx定义的格式转换为xml文件,然后就可以直接在jmeter中使用了。

三、jmeter的jmx测试脚本元素解析

下面介绍一些jmx文件中的基本标签,这些标签都是jmx中最常见,使用这些标签可以生成一个简单的jmeter脚本。

下面我们来看一下一个简单的测试脚本和xml的对应文件,图中的标签和界面中的功能是一一对应的:

1.文档根节点jmeterTestPlan
从jmeter测界面上我们可以看到,测试用例是在一个测试计划下面的,xml文档中也将这个测试计划作为了文档的根节点:

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="2.9" jmeter="3.0 r1743807">
 
</jmeterTestPlan>

2.hashTree
在jmx的XML中可以发现有很多的标签,这个标签是一个用来构造文档树的一个关键标签,这个标签的作用是用来划分功能的树形结构的。这个标签把文档中的内容划分成了不同的层。

3.TestPlan
这个标签位于根节点和hashTree下面,这个标签的作用是用来描述当前的测试计划,可以在这个标签中设置全局变量,这个标签中的全局变量。

4.ThreadGroup这个标签用来描述jmeter的线程组。

6.ConfigTestElement用于配置HTTP请求默认值

7.GenericController用于配置简单控制器,

8.HTTPSamplerProxy用于配置Http请求

9.数据类型标签
stringProp字符串类型,boolProp:布尔类型,intProp整形,collectionProp集合类型。这几个类型在上面8个标签中都存在,用于描述标签中的具体存储信息。如:

<stringProp name="HTTPSampler.method">GET</stringProp>

该例子用于描述这个HTTP请求使用的请求方法为GET请求。

10.elementProp
这个标签用于描述用户定义的变量,如请求默认值,全局变量啊,用户自定义的变量,循环控制器等,这个标签下面可以直接嵌套“数据类型标签”,如下是http请求中的参数定义:

<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
             <collectionProp name="Arguments.arguments">
                <elementProp name="userId" elementType="HTTPArgument">
                  <stringProp name="Argument.value">15914563171</stringProp>
                  <stringProp name="Argument.metadata">=</stringProp>
                  <stringProp name="Argument.name">userId</stringProp>
                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
                  <boolProp name="HTTPArgument.use_equals">true</boolProp>
                </elementProp>
                <elementProp name="passWord" elementType="HTTPArgument">
                  <stringProp name="Argument.value">5B1A453BF15B213BB520A6C845</stringProp>
                  <stringProp name="Argument.metadata">=</stringProp>
                  <stringProp name="Argument.name">passWord</stringProp>
                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
                  <boolProp name="HTTPArgument.use_equals">true</boolProp>
                </elementProp>
              </collectionProp>
            </elementProp>

11.ResponseAssertion用于描述正则表达式提取器

当然还有其他的标签,这里就不一一介绍了,读者可以自己下载一个jmeter,然后通过一个一个添加测试计划中的配置元件来查看jmx文件了解各个标签的作用。

四、根据jmx文件的规则将用例生成xml文档。

生成jmx的用例首先需要具备一些基本信息:用例请求的路径,包含的参数,服务器信息。如果需要做结果校验,还需要提供结果校验的参数,如果需要后置处理器,那么则需要提供后置处理器的参数等等。

jmx文档中为jmeter的每一个可添加到线程组中的功能定义了一个文档标签,这些文档标签使用hashTree这个标签来组织,同级别的标签被放在同一个hashTree中,如果一个标签下面有其他子标签,那么子标签要被放到和父标签同级别的hashTree标签中,elementProp和collectionProp等基本类型标签作为子标签的除外,同级别的不同标签要用一个hashTree的空标签”“分割开。这就是jmx的xml文档最基本的规则了,具体的细节建议自己构造一个复杂的jmx文档去观察。

五、xml文件生成
我们使用Java bean生成XML文档的方式创建jmx文档,首先我们需要根据xml文档的结构定义我们的javaBean。我采用的工具dom4j,
从上面贴出的xml的片段可以看出,每一个标签中都有很多定义的属性,所以定义的javaBean中需要有一个成员变量来专门存储标签的属性,这些属性并不是每一个标签都固定的,为了属性具有可拓展性,直接定义一个属性类来保存这些字段。
如:

public class Attribute {
	private Map<String,String> map;
	
        public Attribute(){
    	   map = new HashMap<String,String>();
        }
	
	public Map<String,String> getMap() {
		return map;
	}

	public void setMap(Map<String,String> map) {
		this.map = map;
	}
	
}

定义了属性类之后我们定义文档标签对应的javaBean,如线程组:

public class ThreadGroup {
	private List<BoolProp> boolProp;
	private Attribute attr;
	private List<StringProp> stringProp;
	private List<LongProp> longProp;
	private ElementProp elementProp;
	
	public ThreadGroup(){
		setAttr(new Attribute());
	}
	
	public Attribute getAttr() {
		return attr;
	}

	public void setAttr(Attribute attr) {
		this.attr = attr;
	}

	public List<BoolProp> getBoolProp() {
		return boolProp;
	}

	public void setBoolProp(List<BoolProp> boolProp) {
		this.boolProp = boolProp;
	}

	public List<StringProp> getStringProp() {
		return stringProp;
	}

	public void setStringProp(List<StringProp> stringProp) {
		this.stringProp = stringProp;
	}

	public List<LongProp> getLongProp() {
		return longProp;
	}

	public void setLongProp(List<LongProp> longProp) {
		this.longProp = longProp;
	}

	public ElementProp getElementProp() {
		return elementProp;
	}

	public void setElementProp(ElementProp elementProp) {
		this.elementProp = elementProp;
	}

}

其他javaBean的定义参照上面的例子。

这样定义之后还有一件事需要做,就是编写javaBean转换为XML的程序,dom4j没有提供直接可用的接口来转换,需要自己编写,dom4j提供的是一个树形结构,需要自己向文档树种插入元素来生成xml.一个java的对象中,往往不单是基本数据类型,而是嵌套了各种对象,对象中又可能还有对象,所以我们可以使用递归的方式来成xml文档。
以下代码是针对jmx文档规则编写的XML解析程序,稍加修改后可用于其他需求,重要的是递归生成xml文档的思路。

/**
 * @ClassName: JmxXmlUtils 
 * @Description: 将对象生成符合JMX文件的XML文档,生成的文档需要在最后再加一个</hashTree>,然后去掉最外层的节点
 * @author zhaochenxi
 * @date 2016年11月24日 下午2:27:38
 */
public class JmxBeanToXml {

	/**
	 * @Title: toElement 
	 * @Description: 将一个对象转换为xml文档,由于对象中有对象,所以采用递归来解析。
	 * @param 
	 * @return void 
	 * @throws 
	 * @author zhaochenxi
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static void toElement(Object object,Element root,Document xmlDoc) {
		if(root==null){
			root=xmlDoc.addElement("jmeterTestPlan");
			root.addAttribute("version", "1.2");
			root.addAttribute("properties", "2.9");
			root.addAttribute("jmeter", "3.0 r1743807");
		}		
		
		if (object != null) {
			if ((object instanceof Number) || (object instanceof Boolean) 
                         || (object instanceof String)|| (object instanceof Double) ||     (object instanceof Float)) {
				root.setText(object.toString());
			} else if (object instanceof Map) {
				mapToElement((Map) object, root);
			} else if (object instanceof Collection) {
				collToElement((Collection) object, root);				
			} else {				
				pojoToElement(object, root);
			}
		} else {
			root.setText("");
		}
	}


	/**
	 * @Title: collToElement 
	 * @Description: 解析集合对象 
	 * @param 
	 * @return void 
	 * @throws 
	 * @author zhaochenxi
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private static void collToElement(Collection<?> coll, Element root) {
		for (Iterator<?> it = coll.iterator(); it.hasNext();) {
			Object value = it.next();
			if (coll == value) {
				continue;
			}
			if ((value instanceof Number) || (value instanceof Boolean) || (value instanceof String)
					|| (value instanceof Double) || (value instanceof Float)) {
				Class<?> classes = value.getClass();
				String objName = classes.getName();
				String elementName = objName.substring(objName.lastIndexOf(".") + 1, objName.length());
				Element elementOfObject = root.addElement(elementName);
				elementOfObject.setText(value.toString());
			} else if (value instanceof Map) {
				Class<?> classes = value.getClass();
				String objName = classes.getName();
				String elementName = objName.substring(objName.lastIndexOf(".") + 1, objName.length());
				Element elementOfObject = root.addElement(elementName);
				mapToElement((Map) value, elementOfObject);
			} else if (value instanceof Collection) {
				Class<?> classes = value.getClass();
				String objName = classes.getName();
				String elementName = objName.substring(objName.lastIndexOf(".") + 1, objName.length());
				Element elementOfObject = root.addElement(elementName);
				collToElement((Collection) value, elementOfObject);
			} else {
				toElement(value, root,null);
			}

		}
		
	};

	/**
	 * @Title: mapToElement 
	 * @Description: 解析Map对象 
	 * @param 
	 * @return void 
	 * @throws 
	 * @author zhaochenxi
	 */
	@SuppressWarnings({ "rawtypes" })
	private static void mapToElement(Map<String, Object> map, Element root) {
		
		for (Iterator<?> it = map.entrySet().iterator(); it.hasNext();) {
			Map.Entry entry = (Map.Entry) it.next();
			String name = (String) entry.getKey();
			if (null == name)
				continue;
			Object value = entry.getValue();
			//不把map作为一个节点
			//Element elementValue = root.addElement(name);
			toElement(value,root,null);
		}
	}

	/**
	 * @Title: pojoToElement 
	 * @Description: 解析对象jmx对象 
	 * @param 
	 * @return void 
	 * @throws 
	 * @author zhaochenxi
	 */
	@SuppressWarnings("unchecked")
	private static void pojoToElement(Object obj, Element root) {
	if(obj instanceof Attribute){
	//如果对象是xml标签的属性
		Attribute attr = (Attribute)obj;
		Map<String,String> map = attr.getMap();
		for (Iterator<?> it = map.entrySet().iterator(); it.hasNext();) {
			Map.Entry<String,String> entry = (Entry<String, String>) it.next();
			String name = (String) entry.getKey();
			if (null == name)
				continue;
			String value = entry.getValue();
			root.addAttribute(name, value);
		      }
		}else{
			Class<?> classes = obj.getClass();
			String objName = classes.getName();

			String elementName = objName.substring(objName.lastIndexOf(".") + 1, objName.length());
			Element elementOfObject = null;
			//JmeterTestPlan这个类名不加入到文档红,因为在根节点上已经添加了。
			if(elementName.equals("JmeterTestPlan")){
				elementOfObject = root;
			}else{
			/** 该类为一个节点 */
				if(elementName.endsWith("Prop")||elementName.endsWith("Tree")){
					elementName =  elementName.substring(0, 1).toLowerCase()+ elementName.substring(1, elementName.length());
				}
				elementOfObject = root.addElement(elementName);
			}
					  
			Field[] fields = classes.getDeclaredFields();
			for (Field f : fields) {
				if (Modifier.isStatic(f.getModifiers()))
					continue;
				String name = f.getName();				
				f.setAccessible(true);
				Object value = null;				
				try {
					value = f.get(obj);
				} catch (Exception e) {
					value = null;
				}
				if(value!=null){
					if(name.equals("value")||name.equals("attr")||name.endsWith("Prop")||name.endsWith("map")){
						toElement(value, elementOfObject,null);
					}else{
						Element elementValue = elementOfObject.addElement(name);
						toElement(value, elementValue,null);
					}
				}
								
			}
		}
	}
	
	
}

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注