zoukankan      html  css  js  c++  java
  • 原创开源项目 扩展iQuery

    在前文开源类库iQuery Android版使用说明类jQuery selector的控件查询iQuery开源类库介绍中,介绍了iQuery的基本用法。

    iQuery是一个开源的自动化测试框架项目,有兴趣的朋友可以在这里下载:

    https://github.com/vowei/iQuery/downloads

    源码位置:
    https://github.com/vowei/iQuery

    iQuery的一个主要目标就是提供一个跨平台的控件查询机制,那就需要考虑如下几个平台差异性:

      • 编程语言的差异,例如iOS可以使用Object-C、JavaScript等语言编程,Android平台使用Java,而Windows 8平台使用C#、C++,网页自动化程序例如Selenium又支持很多编程语言。

        iQuery在设计时就考虑到这些差异性,我们复用antlr这个工具,它已经提供了生成多种编程语言代码的功能,可以很快生成C#、C++、JavaScript、Java、Object-C、Python、Ruby等代码,这样只要维护一套语法就可以了。

        虽然当前只实现了Java和JavaScript的版本,但对其他编程语言的支持也很容易实现,这是iQuery的第一个扩展点。

      • 控件的差异性,不仅各平台有一些不同的控件,例如Android上的ExpandableListView在iOS上就找不到对应的控件,而且同一个控件在不同平台的名字也不一样,例如iOS上的UIASwitch基本上可以等价于Android和Windows 8上的Radio,这样都是在设计iQuery都需要去考虑的。

        针对各平台控件的差异性,iQuery的做法是提供按类型名查询控件的语法,例如在Andorid上可以直接用
        “>> ExpandableListView”

      • 这样的查询语句找到界面上所有的ExpandableListView,而在iOS上可以使用
        “>> UIAScrollView”

      • 而针对同一控件在各平台名字不同的情况,iQuery的做法是提供一个伪类的概念,这个概念也是借自jQuery,例如下面的伪类就统一表示了各平台下可以当作单选框按钮的控件:
        “:radio”

      • 然而,在实现iQuery的时候,我们并不能假设:radio在Android上就是RadioButton控件,在iOS上就应该是UIASwitch,在Windows Phone上就是RadioBox控件,因此我们决定将定义伪类的控制权交给开发者,这是iQuery的第二个扩展点。

      • 控件属性的差异性,例如同是按钮控件,在iOS上就是UIAButton.name(),而在Android上却又是mText属性,甚至有些属性在不同的平台上,有的存在,有的不存在,比如说Android上有mBottom属性,在iOS上就不存在。

        跟解决控件的差异性方法类似,iQuery除了提供按属性名和方法名读取属性值以外,例如
        iOS上的
        “:button [name = ‘确定’]”

      • Android上的
        “:button [mText = ‘确定’]”

      • 为了统一表达控件在各平台通用的属性,iQuery还提供了伪属性的概念,比如上面的例子可以使用下面的查询完成:
        “:button [:text = ‘确定’]”

    • 跟伪类一样,伪属性也是可以由开发者自定义,这是iQuery的第三个扩展点。
    • UI自动化框架的差异,例如 iOS UI自动化测试的框架必然和Andorid UI自动化测试框架不同,但无论如何不同,当今大部分操作系统的图形界面都有下面的性质:
      • 界面由控件树组成。
      • 各控件有属性的概念。

    为了尽可能的支持更多的框架,iQuery将上面的性质封装成两个接口,因此对新平台的支持,只要实现这两个接口就可以了

    本文介绍扩展伪类、伪属性和添加对新平台支持的方法,后续文章会解释支持其他编程语言的做法。

    扩展伪类

    在Java版本中,在iQA.Runtime.jar包里,可以通过iQueryParser. registerPseudoClass这个函数注册一个新的伪类,步骤如下:

    • 使用
    iQueryParser. createParser(String iquery, boolean registerPseudo)
    • 创建一个iQueryParser实例。
      再使用iQueryParser. registerPseudoClass(String name, IPseudoClass func)注册一个新的伪类,例如下面的代码,注册一个名为text的伪类,过滤方式为所有类型名以EditText结尾的控件:
    parser.registerPseudoClass("text", new IPseudoClass() {
            public boolean resolve(ITreeNode node) {
                return filterByNameEndsWith(node, "EditText");
            }
            });

    在JavaScript版本中,暂时不支持扩展伪类的做法,后续版本会添加这个功能。

    扩展伪属性

    在Java版中,通过iQueryParser.registerPseudoAttribute函数注册一个新的伪属性,步骤如下:

    • 使用iQueryParser. createParser(String iquery, boolean registerPseudo)创建一个iQueryParser实例。
    • 再使用iQueryParser. registerPseudoAttribute (String name, IPseudoAttribute func)注册一个新的伪属性,例如下面的代码,注册一个名为bottom的伪类:

              parser.registerPseudoAttribute("bottom", new IPseudoAttribute() {
                  public String resolve(ITreeNode node) {
                      return node.getProperty("mBottom").getValue();
                  }
              });
     

    在iOS的JavaScript版本中的做法是:

      1. 引入以下几个JavaScript文件:
        #import "common.js";
        #import "antlr3-all-min.js";
        #import "iQueryLexer.js";
        #import "iQueryParser.js";
        #import "error.js";
      1. 创建一个iQuery实例:
        var iq = new iQuery(selector);
      1. 注册伪属性:
    iq.parser.registerPseudoAttrs("bottom", function(uiaobj) {
    if ( uiaobj != undefined && uiaobj.rect != undefined ) {
        var rect = uiaobj.rect();
        return rect.origin.y + rect.size.height;
    }
    });

    添加对新平台的支持

    当前支持Java版本的扩展,扩展方式是:

    • 在工程里添加iQA.Runtime.jar包依赖。
    • 实现ITreeNode和IProperty接口。
    • 例如iQuery for Android Instrument的版本就是通过这种方法实现的:

    cc.iqa.iquery.android.SoloTreeNode.java:

    package cc.iqa.iquery.android;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.List;
    
    import android.view.View;
    import android.view.ViewGroup;
    
    import cc.iqa.iquery.*;
    
    public class SoloTreeNode implements ITreeNode {
        private View _view = null;
    
        public SoloTreeNode(View view) {
            _view = view;
        }
    
        public SoloTreeNode(View view, SoloTreeNode parent) {
            this(view);
            _parent = parent;
        }
    
        public View getView() { return _view; }
    
        @Override
        public String getName() {
            return _view.getClass().toString();
        }
    
        public boolean containsProperty(String key) {
            return getMethod(key) != null || getField(key) != null;
        }
    
        public IProperty getProperty(String key) {
            Method method = getMethod(key);
            Object value = null;
    
            if (method != null) {
                try {
                    value = method.invoke(_view);
                } catch (IllegalArgumentException e) {
                    return null;
                } catch (IllegalAccessException e) {
                    return null;
                } catch (InvocationTargetException e) {
                    return null;
                }
            } else {
                Field field = getField(key);
                if ( field != null ) {
                    field.setAccessible(true);
                    try {
                        value = field.get(_view);
                    } catch (IllegalArgumentException e) {
                        return null;
                    } catch (IllegalAccessException e) {
                        return null;
                    }
                }
            }
    
            return new SoloProperty(key, value.toString());
        }
    
        private ITreeNode _parent;
    
        @Override
        public ITreeNode getParent() {
            return _parent;
        }
    
        private List<ITreeNode> _children;
    
        @Override
        public List<ITreeNode> getChildren() {
            if (_children == null) {
                _children = new ArrayList<ITreeNode>();
    
                if (_view instanceof ViewGroup) {
                    addChildren(_children, (ViewGroup) _view);
                }
            }
    
            return _children;
        }
    
        private Method getMethod(String key) {
            Class<?> cls = _view.getClass();
            String getter = String.format("%1$s", key);
    
            try {
                return cls.getMethod(getter);
            } catch (SecurityException e) {
                return null;
            } catch (NoSuchMethodException e) {
                return null;
            }
        }
    
        private Field getField(String key) {
            Class<?> cls = _view.getClass();
            Field ret = null;
    
            while ( ret == null && cls != null ) {        
                try {
                    ret = cls.getDeclaredField(key);
                } catch (SecurityException e) {
                } catch (NoSuchFieldException e) {
                }
    
                if ( ret != null )
                    break;
    
                try {
                    ret = cls.getField(key);
                } catch (SecurityException e) {
                } catch (NoSuchFieldException e) {
                }
    
                cls = cls.getSuperclass();
            }
    
            return ret;
        }
    
        private void addChildren(List<ITreeNode> children, ViewGroup viewGroup) {
            for (int i = 0; i < viewGroup.getChildCount(); i++) {
                final View child = viewGroup.getChildAt(i);
                children.add(new SoloTreeNode(child, this));
            }
        }
    
        @Override
        public String getType() {
            return _view.getClass().toString();
        }
    
        @Override
        public String getText() {
            return getProperty("mText").getValue();
        }
    }

    cc.iqa.iquery.android.SoloProperty.java:

    package cc.iqa.iquery.android;
    
    import cc.iqa.iquery.*;
    
    public class SoloProperty implements IProperty {
        public SoloProperty(String name, String value) {
            this.name = name;
            this.value = value;
        }
    
        private String name;
    
        public String getName() {
            return name;
        }
    
        private String value;
    
        public String getValue() {
            return value;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final IProperty other = (IProperty) obj;
            if (this.name != other.getName()
                    && (this.name == null || !this.name.equals(other.getName()))) {
                return false;
            }
            return !(this.value != other.getValue() && (this.value == null || !this.value
                    .equals(other.getValue())));
        }
    
        @Override
        public int hashCode() {
            int hash = 5;
            hash = 61 * hash + (this.name != null ? this.name.hashCode() : 0);
            hash = 61 * hash + (this.value != null ? this.value.hashCode() : 0);
            return hash;
        }
    }


    本文由知平软件 施懿民编写,请关注我们的微博

  • 相关阅读:
    20155239 2016-2017-2 《Java程序设计》第5周学习总结
    学习Java的必要知识点记录
    # 20155226 2016-2017-2 《Java程序设计》第4周学习总结
    随笔三 第三周学习
    第二周学习
    吕宇轩20155239 第一周
    随笔三 安装Linux操作系统
    随笔二
    随笔一
    java动态代理(JDK和cglib)
  • 原文地址:https://www.cnblogs.com/vowei/p/2654287.html
Copyright © 2011-2022 走看看