接口测试 NO_CODE 接口自动化测试框架

孙高飞 · April 29, 2016 · Last by looshing replied at October 29, 2018 · Last modified by admin 恒温 · 5248 hits

以前一直都是在TesterHome上看文章,也没有想过发帖子,因为我不是做移动端测试的。最近也是看了大家有很多做接口测试的分享 ,自己也有一些想法,所以发上来跟大家分享一下。
我刚来现在的公司1个多月,目前只有我这么一个做测试开发的。之后会招懂代码的帮我,我定的计划暂时就是8个字:分层测试,持续集成。自动化环境部署直接用开发弄得docker镜像。现在也就是在UI自动化,接口自动化和单元测试自动化上下功夫。这一个多月光写测试框架了,接口测试的用例只追了70多条。之后招人做UI自动化和接口自动化,开发会在我搞的测试框架上做单元测试。
好了,背景介绍完毕。现在开始说说接口测试的事。公司有HTTP接口和dubbo接口,开发语言是java,所以想调用dubbo接口只能用java。我也只能用java写了一套框架。
由于暂时会写代码的比较少,所以我做两套框架,一套要写代码,比较灵活。 一套不用写代码,但是不灵活。
先说如何调用dubbo接口吧,dubbo接口说白了,就是个RPC协议的java方法。为了不让手工测试的人写代码,我选择的是定义了一套xml标签规定我需要的信息,然后使用java反射调用接口,并判断返回值和数据库中的数据。直接上例子

<methods methodName="addPurposedNurse" invokeType="Spring" className="com.bj58.daojia.ordercenter.agent.house.NurseOrderService"> 
<params alias="测试正常情况" dataFile="addPurposedNurse.xls">
<in name = "HouseSellerDto" type="Object" path="com.bj58.daojia.ordercenter.dto.house.HouseSellerDto">
<subP name="sellerId" type="Long">22332112345670</subP>
<subP name="realName" type="String">保姆1</subP>
<subP name="phone" type="Long">18701484085</subP>
</in>
<in name="orderId" type="long">12332112345670</in>


<out type="Object" name ="OperationResult" path="com.bj58.daojia.ordercenter.dto.support.OperationResult">
<subP type ="Boolean" name="result">true</subP>

<subP name="message" type="Map" ifverify="false">
<subP name="key" type="Map">
<subP name="key" type="Integer">0</subP>
</subP>
<subP name="value" type="Map">
<subP name="value" type="String">正常</subP>
</subP>
</subP>
</out>

<database database_name="dbwww58com_emclottery">
<table table_name="t_app_order_paidan" where="orderid=12332112345670">
<row>
<field field_name="orderid">12332112345670</field>
<field field_name="sid">22332112345670</field>
<field field_name="stel">18701484085</field>
<field field_name="sname">保姆1</field>
</row>
</table>
</database>
</params>

上面的例子里,就是测试人员填写调用接口的必要信息,例如java类的路径名称,方法名称,入参和返回值等。后台自然就利用java反射的机制调用接口。这个框架需要注意的其实就是参数的类型转换和返回值的验证了。由于这是java接口,而不是http协议那么简单,java是一种强类型语言。从xml文件读取的数据都是string类型。需要做相应的类型转换。所以在xml文件中每个参数(不管是输入参数--in 还是返回值--out)都有一个“type”属性,框架中有相应算法做类型转换。 而由于java中的参数也可以是很复杂的,是一种树形结构。例如一个list里面装的是javaBean,而javaBean中又有一个list类型的属性等。所以读取xml文件的时候就需要递归的遍历整个树结果,然后同时也是递归调用相应的类型转换算法加以转型。我说点实现细节吧

上面就是目前所有的转型算法,抱歉媳妇的电脑没有IDE,我只能截这么个图了。所有的算法实现TypeConvert这个接口,通过策略模式调用算法。内部外部都通过TypeConvertFactroy这个工厂类去创建转型算法对象。

public class TypeConvertFactroy {
// private static ConcurrentHashMap<String, TypeConvert> map = new
// ConcurrentHashMap<String, TypeConvert>();

public static TypeConvert createTypeConvert(String type) {
if (type == null || type.equals("")) {
type = "String";
}
String beanName = Tools.convertString(type) + "Convert";
return (TypeConvert) SpringContext.getBean(beanName);

/*
* if (map.containsKey(type)) { return map.get(type); } else {
* TypeConvert obj = null; try { type =
* com.bj58.daojia.test.InterfaceTool
* .data.paramLoader.typeConvert.Tools.convertString(type); obj =
* (TypeConvert) Tools .reflectObject(
* "com.bj58.daojia.test.InterfaceTool.data.paramLoader.typeConvert." +
* type + "Convert"); map.putIfAbsent(type, obj); } catch (Exception e)
* { e.printStackTrace(); Assert.assertTrue("没有找到 " + type +
* " 的参数类型,请核对是否输入错误的参数类型或请在系统中增加对应的参数类型", false); }
*
* return obj; }
*/


}

}

工厂方法里也是用spring的bean工厂管理去创建对象的。 本来之前没使用spring的时候是用的被注释的那段java 反射代码去生成对象的(本人很喜欢java反射)。 这么设计的原因是为了可扩展性。以后加新的转型算法的时候,只要在特定的路径下创建复合命名规则的算法类就可以了。而不必加入新的代码。所有的算法存在内存中,使用表驱动的方式,存在一个线程安全的map中:ConcurrentHashMap以防以后多线程环境的调用。

验证返回值的机制也是类似的。如下图。

每一种类型的验证都对应一个验证算法。 不过与入参不一样是,无法使用表驱动的方式去获取算法对象,因为你无法用一个简单的方式来判断当前的返回值该由哪个算法去处理。尤其是javaBean类型,根本没有任何办法判断当前对象是一个javaBean。所以我使用责任链模式组织这些算法。如下图,这个单例的工厂类负责组装责任链

public class VerifyHandlerFatory {
public static void main(String[] args) {
System.out.println(VerifyHandlerFatory.createVerifyHandler());
}

private VerifyHandlerFatory() {
}

public static VerifyHandler createVerifyHandler() {
return VerifyHandlerHolder.first;
}

public static class VerifyHandlerHolder {
private static VerifyHandler first = createVerifyHandler();

/**
* 组装验证责任链,并返回第一个节点的对象
*
* @return
*/

public static VerifyHandler createVerifyHandler() {
// 第一个节点为基本数据类型,这个类型的参数最多
first = new PrimitiveType();
VerifyHandler ListType = new ListType();
VerifyHandler mapType = new MapType();
VerifyHandler jSONObjectType = new JSONObjectType();
VerifyHandler enumType = new EnumType();
VerifyHandler collectionType = new CollectionType();

// 最后一个节点为Object:因为无法判断具体的Object类型,所以放在最后一层,不属于其他类型的就是Object
VerifyHandler ObjectType = new ObjectType();

// 开始组装责任链
first.setHandler(ListType);
ListType.setHandler(mapType);
mapType.setHandler(jSONObjectType);
jSONObjectType.setHandler(enumType);
enumType.setHandler(collectionType);
collectionType.setHandler(ObjectType);
return first;
}
}
}

具体原理就是把期望的对象和实际的返回值传递给链表的第一个节点。第一个节点判断当前对象类型是不是自己应该处理的。如果是,就处理,不是就传给下一个节点判断,一次类推,一直到最后一个节点--javaBean类型。由于无法判断对象是否是javaBean类型,所以其他所有节点都判断不是自己该处理的类型了,那说明就是javaBean类型。下面贴一段基本类型的验证代码。

public class PrimitiveType extends VerifyHandler {

@Override
public Boolean PassRequest(Object actualValue, Object expectedValue, String fieldName_no) {
if (expectedValue.getClass().isPrimitive() || expectedValue.getClass().isAssignableFrom(String.class)
|| expectedValue.getClass().isAssignableFrom(Boolean.class)
|| expectedValue.getClass().isAssignableFrom(Integer.class)
|| expectedValue.getClass().isAssignableFrom(Double.class)
|| expectedValue.getClass().isAssignableFrom(Float.class)
|| expectedValue.getClass().isAssignableFrom(Short.class)
|| expectedValue.getClass().isAssignableFrom(Byte.class)
|| expectedValue.getClass().isAssignableFrom(Long.class)
|| expectedValue.getClass().isAssignableFrom(Float.class)
|| expectedValue.getClass().isAssignableFrom(Date.class)) {


Assert.assertEquals("返回值与预期值不符:" + fieldName_no, expectedValue, actualValue);
return true;
} else {
return nextHandler.PassRequest(actualValue, expectedValue, fieldName_no);
}
}
}

到此这个框架中最难处理的部分就说完了。虽然很笼统,但是就说个大概的思路,有个思路大家就知道怎么回事了。但是现在还有一点需要说明的是,说到现在都只是在说接口调用的问题。还有一个问题也很重要,那就是测试数据的准备和销毁问题。 我看到很多其他人的框架,不论是http接口的还是RPC协议的。都是在模拟调用,判断返回值。但是都没有说到测试数据的准备和销毁。 我们都知道,测试数据决定了我们的测试用例是否能重复执行。只有重复执行了,这个才算是真正的自动化。 我看很多人做的接口自动化,实际上根本就没有自动化,因为他需要测试人员手工准备测试数据。 我不知道其他公司是怎么解决这个问题的。我解决的方式很简单粗暴。 框架直接提供一种机制直接往数据库里插入测试数据,再由框架负责销毁他们。还记得xml里面有一个dataFile的标签属性么?这个dataFile就是一个excel文件,里面的行和列就对应着数据库的行和列,xml文件指定数据文件的路径,框架负责读取这个excel文件,拼接出insert和delete语句。为数据库执行创建和销毁操作。文件例子如下:

第一行是库和表的名字,以下的就是数据信息。 实际上这个文件可以用很多工具自动生成出来。我们用的navicate for mysql 就可以导出excel格式的文件。所以我们的思路都是第一次在UI上创建测试数据,然后导出excel文件,稍作修改便用在自动化上了。也满方便的。用例执行前创建数据,用例结束后,销毁数据。 当然如果被调用的接口内部创建出的数据就无法删除了。 所以我在读取excel文件的代码里加了一段逻辑。 凡是delete开头的sheet中的数据只删除 ,不创建。 这样可以定义销毁接口本身创建的数据。

哦, 对了, 还有一个比较关键的是,我看很多人做的接口测试框架只验证返回值,而不去管数据库中的记录。我觉得这也是验证不完全的。所以我在xml里还定义了一套标签是这样的:

<database database_name="dbwww58com_emclottery">
<table table_name="t_app_order_paidan" where="orderid=12332112345670">
<row>
<field field_name="orderid">12332112345670</field>
<field field_name="sid">22332112345670</field>
<field field_name="stel">18701484085</field>
<field field_name="sname">保姆1</field>
</row>
</table>
</database>

这套标签专门拼sql验证数据库。

好了,这套框架的XML版本的主要思路就介绍到这了。当然还有很多细枝末节,例如如何获取dubbo协议的对象,如何数据驱动等就不一一介绍了。 这个框架的问题在与,这始终还是java接口。如果使用框架的人对java一无所知的话,仍然很难使用。例如他可能根本就不知道什么是List什么是javaBean。0基础的人还是很难做的下去。再一个很复杂的接口在XML中的定义也会很复杂。不如直接写代码来的方便。 这是个问题, 所以我这里也是有一个需要写代码的框架。其实跟这个功能一样的。只不过是直接在代码里调用这些个xml里的功能。以后会写代码的测试多了,也许就会用那个版本的框架了吧。

好了,今天先说到这吧。关于UI自动化框架和单元测试的框架以后再分享。

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

自己做这套东西的初衷是什么?市面上没有满足尼需求的开源工具么

很不错的想法,也继续鼓励在社区发帖子~

dubbo 用socket 去处理就行

—— 来自TesterHome官方 安卓客户端

很不错,毕竟要给不会代码的测试用

—— 来自TesterHome官方 安卓客户端

#1楼 @doctorq 我就是用testng驱动的。不是说一点开源框架不用的。

#2楼 @monkey 多谢支持

#4楼 @sordar 但还是感觉不会写代码的人来做这种接口的测试比较困难。我在考虑要不要不这么弄

由于暂时会写代码的比较少,所以我做两套框架,一套要写代码,比较灵活。 一套不用写代码,但是不灵活。
--写代码和不写代码为什么要做两套框架,这两者并不互斥!

孙高飞 #9 · May 03, 2016 作者

#8楼 @quqing 基本是一套。但是两种形式。一个写代码一个不写代码。在领导那看。就跟两套一样

不错的,我们也在做HTTP接口和dubbo接口自动化

#10楼 @success 呃,我有个疑惑,dubbo接口自动化只能通过读取xml文件,java反射的方式去动态生成测试接口了么?

孙高飞 #12 · May 03, 2016 作者

#11楼 @sigma 不是啊。写代码显示的调用是最常用的。不过我们当时没几个会写代码的。所以我就搞成这样给他们用了

#11楼 @sigma 他这里应该是给那些不用写代码,只写些配置文件的人用的吧。当然,你也可以全部写代码去调用。每个公司都有自己的要求。关键还是看领导喜欢什么模式,哈哈

#12楼 @ycwdaaaa 我之前写过一个特别简单的小程序,RPC的,我这边需要拿到研发的接口代码啥的,然后都注册到zk上,之后调用,你那个我看着很复杂(高大上),相当于你定义了文件格式,然后动态地去生成这些接口的java文件作为client端么

#13楼 @success 哈哈,领导要是喜欢看不停干活不停敲代码的,那我就直接每个接口写个testmethod,不做啥数据驱动的框架之类的~

#12楼 @ycwdaaaa 还有个问题,你最后那个check db的xml,是针对于本次测试所有需要校验的字段写成一个大的xml,里面包含了很多子节点,然后拼接select orderid,xxx, xxx,xxx from t_app_order_paidan where orderid=12332112345670执行sql后,再根据标签中的值做验证么?支持那种可随意配置需要验证db和不需要验证db么(比如我去指定目录去遍历解析这个xml,没找到就不做验证)?

孙高飞 #17 · May 03, 2016 作者

#16楼 @sigma 是这样的,找不到就不验证。找到了就验证

#17楼 @ycwdaaaa 哈哈,读了你的文章真是对我启发很大,非常感谢你耐心的回复~我也准备自己实现下试试~~期待你之后的系列文章~

孙高飞 #19 · May 03, 2016 作者

#18楼 @sigma 别期望太大~~其实没多少内容。这些其实只是没人整理一下。其实明白了形式。你研究研究就都实现了。

#19楼 @ycwdaaaa 哈哈,关键我就差在这些设计思想与形式上,有些东西自己有可能想半天或者实验半天都没有很好的效果(但是绝对不会自己不思考),看看其他人的文章,说不定就通了,而且我比较初级,先从模仿开始喽~

有个问题请教下,你有用类似orm的框架吗,用的是哪个?
还是你是直接用sql语句的啊?

孙高飞 #22 · May 04, 2016 作者

#21楼 @sziitash 我用的mybatis

现在自己很菜,好多不理解,但对楼主写的这些比较感兴趣,请问楼主有没有开博客,想多了解些

孙高飞 #24 · May 07, 2016 作者

#23楼 @xiaobaicai123 暂时还没有博客~

给手工测试的人使用,需要编辑xml文件吗?我很久以前做过一个小工具,使用xml来输入数据和一些简单配置。比楼主上面演示的例子要简单的多。但是一点不懂开发的人还是很抗拒通过编辑xml文件来使用小工具。最后是做了一个图形界面来间接编辑xml。

孙高飞 #26 · May 09, 2016 作者

#25楼 @frankliu 凡是抗拒的一律镇压哈哈哈, 开玩笑啦. 后来写了个能自动生成xml的工具

#25楼 @frankliu 隐约看到自动化测试平台的影子哇~~

#26楼 @ycwdaaaa 你这篇文章给了我很大的启发,尤其是最后的落地数据校验,我仿照你那个xml写了个json版的,但是发现数据库字段读取出来(Java & mysql),比如Date类型,给我自动带了个.0出来,就像2011-09-09 09:09:30.0这个样子,不知你有没有遇到过类似的问题,或者需要自己实现一个convert支持各种数据库字段类型的转换?

孙高飞 #29 · May 10, 2016 作者

#28楼 @sigma 我是专门判断是不是data类型,如果是就把最后那个0去掉

@ycwdaaaa ,其实比较返回值也可以用SpringContext.getBean(type + "Verfier") 这种方式找到某个Verfier,然后用Verfier.verify去比较结果,这样做有2个好处
1 添加新类型只要加一个文件,不像责任链还要把这个新类型串入责任链
2 不但验证了返回值还验证了返回类型。
你觉得呢

孙高飞 [Topic was deleted] 中提及了此贴 28 Jun 18:51
孙高飞 [Topic was deleted] 中提及了此贴 28 Jun 18:51

你好,能留个联系方式聊下么?

孙高飞 #34 · July 06, 2016 作者

#33楼 @lilithlovesyou 446051551是我qq

#3楼 @taki
具体怎么调用呢,能给个DEMO么

其实第二种框架对于代码的要求并不高,如果是公司内部使用,培训几次,写一些简单的接口测试类并不难,只要框架封装好

—— 来自TesterHome官方 安卓客户端

定义销毁接口本身创建的数据这个具体是怎么做的呢,目前我这样做只能去看开发逻辑才能去删除响应数据

#37楼 @tcat 你可以看看我写的监控式数据管理方式,我通过assertJ的changes概念写了个工具。 自动对比用例执行前和执行后数据库中数据的变化并拼出sql语句,将数据恢复回去。

孙高飞 测试开发之路--持续集成 中提及了此贴 23 Nov 08:52
孙高飞 测试开发之路----数据驱动及其变种 中提及了此贴 10 Dec 11:45

自动化的难点就在维护 和数据的处理上,对于我们这数据库只能查询权限来说,没有好的办法,只能通过sql查询想要数据。

写得真好,赞一个!

@ycwdaaaa 楼主,最近我要写Dubbo接口的自动化测试用例,想测试接口里面的逻辑,不知道如何搭建框架,可以给一些提示吗?

你好,可以贴下ListConvert怎么实现的吗?

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up