这个是我后来写的一本书,http://www.ituring.com.cn/minibook/10775。这个是我后来找到的自动化完美解决方案。
在上一篇中讲述如何读取xml然后执行相应的java类,用到了java反射的一些知识,在这一讲中,我们会继续提到反射,也许你可能会问为何反射在这里会使用那么多,就其原因,java不像ruby 或者python是一种动态脚本语言。所以在使用的有很多限制,这也是为什么读一个xml而频繁使用反射的原因。
那我们先看下执行验证的脚本
<Script>
<FirstDemo name="testjingdongcom" password="jingdong" productId="286048"/>
<!-- 执行验证的脚本 -->
<Assert name="assert" value="testjingdongcom.productId" expectedValue="94005" compare="EQ" />
</Script>
我们在接着看第一章的demo中,有个把页面显示的订单号抓取下来的步骤
String s = selenium.getText("xpath=id('part_cart')//div[@class='middle']//tr[@class='align_Center']/td[1]");
那我接下来就要创建验证这个类,对这种得到的类型字符串做验证,验证比如大于小于等于某个数,里面包不包含某个字符等等。
public class Assert extends JspDemo{
private List<String> values = new ArrayList<String>();
private List<String> expectedValues = new ArrayList<String>();
private String compare;
public void getValueFromXml(Element e) {
values=AnalyzeScript.analyzeXML(e, "values");
expectedValues=AnalyzeScript.analyzeXML(e, "expectedValues");
compare=AnalyzeScript.analyzeXML(e, "compare");
}
boolean result = false;
// == | equals
if ("EQ".equalsIgnoreCase(compare)) {
result = value.equals(expectedValue);
}
// != | !equals
else if ("!EQ".equalsIgnoreCase(compare)) {
result = !value.equals(expectedValue);
}
// <
else if ("LS".equalsIgnoreCase(compare)) {
result = Double.parseDouble(value) < Double
.parseDouble(expectedValue);
}
// <=
else if ("LSE".equalsIgnoreCase(compare)) {
result = Double.parseDouble(value) <= Double
.parseDouble(expectedValue);
}
// >
else if ("GT".equalsIgnoreCase(compare)) {
result = Double.parseDouble(value) > Double
.parseDouble(expectedValue);
}
// >=
else if ("GTE".equalsIgnoreCase(compare)) {
result = Double.parseDouble(value) >= Double
.parseDouble(expectedValue);
}
//字符串包含
else if ("CON".equalsIgnoreCase(compare)) {
result = value.contains(expectedValue);
}
// not supported operator
else {
throw new Exception("not supported operator " + compare);
}
return result;
}
这样一个字符串验证的类是很简单的,难点来了,我如何把xml指定的上一个类执行后所得到的要验证的字符串等传递到下一个要执行的类Assert中。
下面讲述一种思路,在反射中对构造器传参数。
package action;
import java.lang.reflect.Constructor;
import java.util.Hashtable;
public class Construct extends PP {
private Hashtable t;
public Construct(Hashtable s){
t=s;
System.out.println(this.toString());
}
public String toString(){
return t.get("tt");
}
public static void main(String[] args) throws Exception{
Hashtable<String,String> sd=new Hashtable<String,String>();
sd.put("tt", "tt");
Class c=Class.forName("action.Construct");
Object[] parameters={sd};
Class[] argtypes={Hashtable.class};
Constructor strIntCtor=c.getConstructor(argtypes);
Object inst = strIntCtor.newInstance(parameters);
}}
开始。我们在父类Demo类里加入构造器,并赋初值
private Element el;
private Hashtable<String,String> t
public Demo(Element e,Hashtable<String,String> s){
this,el=e;
this.t=s;
}
//并加入一个作为传值的hashmap
private HashMap<String, String> readOnly = new HashMap<String, String>();
/**
* get hashmap of readOnly
* @return readOnly
* @author Jay Yang
*/
public String getReadOnlyVariable(String key) {
return this.readOnly.get(key);
}
/**
* set read only variables
* @param key
* @param value
*/
protected void setReadOnlyVariable(String key, String value) throws rException {
this.readOnly.put(key, value);
}
好的,子类继承它的时候继承这个构造器。比如在Assert中在继承中实现构造器
public Asserto(Element e,Hashtable refs){
super(e, refs);
}
把得到的值传过来就行了。
那我如何放入值供下一类用呢,也就用setReadOnlyVariable(String key, String value)就行了。
还记得在上一节中说过的通过反射执行类的方法吗,就是这个
//通过反射找到对应的java类
Class clazz=Class.forName(e.getNodeName());
Demo instance=(Demo)clazz.newInstance();
//得到刚才模板模式中定义好的run方法,注意new Class[] { Element.class }和下面的m.invoke(instance,e)都是表示在反射中如何把参数传入对应的方法中。
Method m = clazz.getMethod("run", new Class[] { Element.class });
m.invoke(instance,e);
现在我们改下,改成可以把上一个类中得到的值可以在下一个类比如Assert中使用。
private Hashtable<String, Object> objectLookup = null;
private Element e;
Class clazz=Class.forName(e.getNodeName());
Object[] parameters = {e, this.objectLookup};
Class[] argtypes = {Element.class, Hashtable.class};
Constructor strIntCtor = businessModuleClass.getConstructor(argtypes);
Object inst = strIntCtor.newInstance(parameters);
//以xml中name的属性做键值
String key = ((Element) n).getAttribute("name");
if (!(this.executionList.contains(key) || this.objectLookup.containsKey(key))) {
this.objectLookup.put(key, inst);
}
当通过这种方法把上一个类中得到的字符串放入Assert类中构造器后,在Assert中通过识别传入的特点字符得到键值解析它就行了
<Assert name="assert" value="testjingdongcom.productId" expectedValue="94005" compare="EQ" />
这里的value="testjingdongcom.productId" 中testjingdongcom.productId就是键值。
下面就是Assert类中如何解析的,如果没键值,
protected String getValueFromXml(String attName) throws DataDrivenException{
return this.refObj.checkReference(this.refObj.getAttValue(e, attName));
}
如果有键值。
protected String getValueFromXml(String attName, String separator) throws DataDrivenException {
return this.refObj.checkReference(this.refObj.getAttValue(e, attName), separator);
}
下面是我现在的工程中refObj.checkReference方法,仅供参考,实际应用中你可以根据自己的需要编写。
public String checkReference(String s) throws DataDrivenException {
if (s != null) {
s = s.trim();
if (s.length() == 0) {
return null;
}
//resolve ref for Data-driven
if ((s != null) && s.charAt(0) == '[' && (s.charAt(s.length() - 1) == ']')) {
s = this.btc.getData(s.substring(1, s.length() - 1));
}
//resolve ref from object list
if ((s.charAt(0) == '{') && (s.charAt(s.length() - 1) == '}')) {
Object retObj = this.resolveReference(s);
s = (retObj == null) ? null : retObj.toString();
}
}
return s;
}
/**
* To retrieve data if it is reference of data-driven or from module object list, separated by the appointed separator if there are more than one data.
*
* @param s string to check
* @param separator the appointed separator, only "|", ",", ";", "/", "`" are valid
* @return referenced datas
* @throws DataDrivenException
* @author mwu
*/
public String checkReference(String s, String separator) throws DataDrivenException {
// to validate the separator
List<String> validSeparators = Arrays.asList("|", ",", ";", "/", "`");
if (!validSeparators.contains(separator)) {
throw new DataDrivenException(this.getClass().getName() + " :: separator can only be one of " + validSeparators.toString());
}
if (s != null) {
s = s.trim();
if (s.length() == 0) {
return null;
}
//resolve ref for Data-driven
if ((s != null) && s.charAt(0) == '[' && (s.charAt(s.length() - 1) == ']')) {
s = this.btc.getData(s.substring(1, s.length() - 1));
}
//resolve ref from object list
String moduleNameRegex = "[^." + separator + "]+";
String tempSeparator = separator;
if ("|".equals(separator)) {
tempSeparator = "\\|";
}
String regex = "^\\{" + moduleNameRegex + "(\\.[a-zA-Z\\$_][\\w\\$_]*)?\\}(" + tempSeparator + "\\{" + moduleNameRegex + "(\\.[a-zA-Z\\$_][\\w\\$_]*)?\\})*$";
if (StringUtil.IsMatchFound(s, regex)) {
StringBuffer buffer = new StringBuffer();
String[] parts = s.split(tempSeparator);
String temp = null;
for (String part : parts) {
Object retObj = this.resolveReference(part);// part is like: {aaa.bbb}
temp = (retObj == null) ? null : retObj.toString();
if (temp == null) {
System.out.println("********** WARN: The value of " + part + " is null! **********");
}
buffer.append(temp);
buffer.append(separator);
}
s = buffer.toString();
s = s.substring(0, s.length() - 1);// remove the last separator
}
}
return s;
}
呵呵,这种方法是不是很复杂,为了传一个值搞的那么麻烦。其实有一个更好的方法,我们平常在用structs框架的时候,用过session没。
我们可以通过把值直接给session,执行下一个类想使用,也直接从seesion中取就行了。
我们可以自定义个session。
public static synchronized SessionContext getSessionContext() {
String threadName = Thread.currentThread().getName();
SessionContext context = sessionContextMap.get(threadName);
if (context == null) {
context = new SessionContext();
sessionContextMap.put(threadName, context);
}
return context;
}
这个用法类似structs中session具体用法就不细说啦。