自动化工具 自研测试框架中反射的应用

Alpha · 2017年11月21日 · 最后由 Alpha 回复于 2017年12月06日 · 1086 次阅读

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

框架存在的意义,很重要的一点在于通过框架来促成多人协作的开发模式,达到一定程度上人力成本的累加等同于开发成本的累加。所以,一旦框架形成,绝对不会再让所有脚本开发人员根据接口规范逐一的去写调用接口代码,而会逐步从以下两点进阶考虑: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;
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

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 4 条回复 时间 点赞

迫不及待的想看测试报告和测试集配置😂

控制反转、依赖注入。

markdown 格式噢

rywu 回复

那个外层框架都会写的。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册