自研测试框架中反射的应用

(原创文章,转载请注明出处。)

    之前有提到过,自己曾基于公司业务系统从无到有码过一套测试框架。今天我们就来谈一下这套框架的一小部分,也是较为核心和基础的一部分,反射机制的应用。这块也是框架实现关键字驱动或者数据驱动的基础。

    框架存在的意义,很重要的一点在于通过框架来促成多人协作的开发模式,达到一定程度上人力成本的累加等同于开发成本的累加。所以,一旦框架形成,绝对不会再让所有脚本开发人员根据接口规范逐一的去写调用接口代码,而会逐步从以下两点进阶考虑:1、做接口调用代码的封装,脚本开发人员直接调用。这种方式优点在于层次清晰,适合测试开发人员直接开发代码化的测试脚本,任何问题都可以直接Debug到接口调用的位置,快速排错。缺点也显而易见,不适合一般测试人员或者业务人员使用,或者说使用者无法把精力集中在业务逻辑的编排及验证点的确认上。2、做关键字的封装,开发人员直接写关键字名即调用该接口。概念上非常容易理解,因为大家QTP、RF等已经用了很多了。但框架开发本身需要面对的是,如何在框架运行的过程中,根据关键字动态的调用到相关的接口。这里我们举个简单的例子让大家理解:比如现在有Open Browser和Do Login两个接口。框架中要实现这样的逻辑,if(keyword == "Open Browser" )Open Browser(); else if(keyword == "Do Login")Do Login()...,并且还需要有返回值的处理。按照我这种写法的话就需要写无穷无尽的else if,那我们能不能将所有的if统一成一个接口,类似exec(keyword, parameters[]...)的形式,也就是delegate的概念呢?我们就着重讨论这一块的内容。

    技术上并没有什么疑难点,关键还是针对被测系统的接口和返回值做设计。

    关于接口,这里我们假设有一个接口对象的类库叫ObjectLib,里面有一个基类叫baseObject,基类里定义了执行函数exec,定义了接口通用返回值的RootObject类。(exec可以是任意类型的接口调用,比如Rest接口,RootObject可以存放任意的公共返回类型,比如http header里的返回状态码或body里的Json。)所有被测接口都会出现在类库中,并继承这个基类。

 

下面我们开始调用的过程(仅包含核心代码):

一、获取被测接口类:

     ObjectLib _Obj = new ObjectLib ();
     object obj = Reflection.getObject(interfaceName);

 

第一句话两个作用,首先是动态生成接口对象的类库,以便下一句能够根据类名和方法名定位对象。其次是可以顺便做一些初始化设置,比如服务器或数据库的连接设置等。

第二句就利用反射直接获取对象了,具体为:

            Assembly assembly = Assembly.GetEntryAssembly();
            object obj = assembly.CreateInstance("ObjectLib +" + className);
            return obj;

 

这里的className就是我们需要获取的interfaceName,最终得到需要的obj。

 

二、执行方法:

                Type type = obj.GetType();
                MethodInfo methodinfo = type.GetMethod(methodName);
                methodinfo.Invoke(obj, parameters);

 

这个过程也非常的简单,就是一般反射调用Invoke执行的过程。这里唯一要注意的是parameters的处理,针对不同的接口要求需要做不同的处理,比如post和get的传参方式就不同。

 

三、返回值的获取:

返回值的解析要根据返回对象的不同做不同层级的处理。以下以Json为例列举几种类型的处理:

1、header里的状态码及body里的Json最外层执行结果获取,类似以下代码:

                Type type = obj.GetType();
                FieldInfo field = null;
                Hashtable ht = new Hashtable();
                if (memberName == "RootObject")
                {
                    field = type.GetField("rootObject");
                }
                if (field != null)
                {
                    object _obj = field.GetValue(obj);
                    Type _type = _obj.GetType();
                    foreach (PropertyInfo propertyInfo in _type.GetProperties())
                    {
                        ht.Add(propertyInfo.Name, propertyInfo.GetValue(_obj, null));
                    }
                }
                return ht;

 

以上是逐层获取的代码,比较通用,根据Json格式的不同稍加改写就可以复用。在当前情况下,也可以简写为:

getProperties(obj, "RootObject", BindingFlags.Static); 私有的话还要 | BindingFlags.NonPublic|

 

2、其他层级的数据:

其他层级的数据需要实际问题实际分析,比如Json里包含有List的情况:

                object _obj = field.GetValue(obj);
                Type _type = _obj.GetType();
                if (_type.Name == "List`1")
                {
                    var list = _obj as IEnumerable<object>;
                    foreach (var item in list)
                    {
                        Type _typeList = item.GetType();
                        PropertyInfo[] ps = _typeList.GetProperties();
                        Hashtable ht = new Hashtable();
                        foreach (PropertyInfo propertyInfo in ps)
                        {
                            ht.Add(propertyInfo.Name, propertyInfo.GetValue(item, null));
                        }
                        propertiesList.Add(ht);
                     }     
                 }

 

这里用了IEnumerable,它返回一个可用于循环访问集合的IEnumerator对象。IEnumerator对象有什么呢?它是一个真正的集合访问器,没有它,就不能使用foreach语句遍历集合或数组。所以根据特定的数据类型,需要通过多次调试确定正确的方法。

写到这里,核心内容就叙述完了。接下来就是要通过外部组件的合理配置,以达到接口关键字驱动、数据驱动的目的了。

 

--Alpha

posted on 2017-11-21 17:19  泽南-Alpha  阅读(510)  评论(0编辑  收藏  举报

导航