NO_CODE 接口自动化测试框架

来源:https://testerhome.com/topics/4767

以前一直都是在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自动化框架和单元测试的框架以后再分享。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值