自动化工具 Avatar--行走的接口自动化框架 (支持 dubbo、http/https、mysql)

在路上 for 乐视商城 · 2018年01月29日 · 最后由 在路上 回复于 2018年08月25日 · 21925 次阅读
本帖已被设为精华帖!

自评:

读了 @ 孙高飞大佬的文章后,知道我这篇文章没有成为精华帖的原因了,因为浮于表面,深度不够。需要反省了

背景

因一直从事服务端测试,在人员极速减少的情况下,故开发了一套提升效率的接口自动化框架,用于提升测试效率。

github 源码地址:

https://testerhome.com/opensource_projects/avatar

序

该框架功能完善和性能优化将会一直在路上。

1、Avatar 框架图

废话不多说,框架支持功能和依赖见下图:

2、已实现功能及报告展示

目前可测试 dubbo、http/https、mysql
邮件报告内容如下:

附件内容如下:

3、Avatar 代码结构图

废话不多说,框架代码结构及说明见下图:

4、详细功能及使用说明

4.1 dubbo 接口测试

4.1.1 pom.xml 中添加 dubbo provider 的 maven 库

示例:

<!-- maven仓库配置 -->
    <distributionManagement>
        <snapshotRepository>
            <id>xxxx-snapshot</id>
            <name>xxxx Snapshot</name>
            <url>http://maven.xxxx.cn/nexus/</url>
        </snapshotRepository>
                <repository>
            <id>xxxx-release</id>
            <name>xxx Release</name>
            <url>http://maven.xxxx.cn/content/repositories/</url>
        </repository>
    </distributionManagement>
    <!-- 仓库地址配置,可以放置到maven工具的conf目录setting.xml进行全局配置 -->
    <repositories>
        <repository>
            <id>xxxx-public</id>
            <name>xxxx-public</name>
            <releases>
            </releases>
            <snapshots>
            </snapshots>
            <url>http://maven.xxxx.cn/nexus/content/</url>
        </repository>
                <repository>
                ......
                </repository>
    </repositories>

4.1.2 dubbo-config.xml 中配置 zookeeper 和 dubbo 接口

示例:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://code.alibabatech.com/schema/dubbo
       http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <dubbo:application name="demo-consumer"/>

    <!-- zookeeper地址配置 -->
    <dubbo:registry address="zookeeper://10.110.112.119:2181" />

    <!-- id定义为接口名,version需要和开发核实当前的接口version -->
    <dubbo:reference id="IPromiseForOrderService" interface="com.order.promise.api.IPromiseForOrderService" version="1.0" check="true"/>
    <dubbo:reference id="OrderStatusService" interface="com.order.status.flow.service.OrderStatusService" version="1.2" check="true"/>
</beans>

4.1.3 导入 dubbo 接口参数化文件

dubbo 参数化文件内容如下:

4.1.4 编写测试用例

public class TestIPromiseForOrderService {

    //不需要修改:固定值变量
    private static final String FILE_PATH = "/src/test/TestCaseExcelData/dubbo/";  //文件路径
    public static final ApplicationContext CONTEXT = new ClassPathXmlApplicationContext("dubbo-config.xml");
    private GetTestCaseExcel excelData;


    //1、fileName是dubbo接口的包名, caseName是dubbo接口名
    private String fileName = "com.order.promise.api"; //dubbo接口的包名作为文件名,不包含文件后缀.xls
    private String caseName = "IPromiseForOrderService"; //dubbo接口名作为sheet名


    //2、request随着dubbo接口的不同,需要定义指定类型
    private FreeStockForOrderParam requestParam = new FreeStockForOrderParam();


    //3、新建dubbo的消费者对象,需要修改类名
    private IPromiseForOrderService ClassConsumer = (IPromiseForOrderService) CONTEXT.getBean(caseName);


    //不需要修改: 从excel文件中读取数据,不需要修改
    @DataProvider
    public Object[][] Numbers() throws BiffException, IOException {
        excelData = new GetTestCaseExcel(FILE_PATH, fileName, caseName);
        return excelData.getExcelData();
    }

    //不需要修改:dubbo接口访问Provider
    @Test(dataProvider = "Numbers")
    public void test(HashMap<String, String> data) {

        //4、从excel中取出各项参数
        String orderNo = data.get("orderNo");
        String operater = data.get("operater");
        String channel = data.get("channel");
        String operateId = data.get("operateId");
        String version = PromiseVersion.V_1_0_0.getVersion();
        String status = data.get("Status");
        String message = data.get("Message");
        String Result = data.get("Result");

        //5、将请求参数导入对象requestParam中
        requestParam.setOrderNo(orderNo);
        requestParam.setOperater(operater);
        requestParam.setChannel(channel);
        requestParam.setOperateId(operateId);
        requestParam.setVersion(version);

        //6、定义dubbo接口请求参数,修改类名
        RestRequest<FreeStockForOrderParam> request = new RestRequest<FreeStockForOrderParam>();

        //不需要修改:向生产者发送请求
        request.setRequest(requestParam);

        //7、修改dubbo接口方法名 和 responseData类型
        RestResponse responseData = ClassConsumer.freeSaleStock(request);

        //8、自定义log记录内容及校验数据(根据responseData来判断dubbo接口是否请求成功)
        if (responseData!=null) {
            String responseStatus = responseData.getStatus().toString();
            String responseMessage = responseData.getMessage().toString();
            if (responseStatus.equals(status) && responseMessage.equals(message)) {
                Reporter.log("期望的Status:" + status + ",期望的Message:" + message + "\n" + "实际的Status:" + responseStatus + ",实际的Message:" + responseMessage);
                System.out.println("期望的Status:" + status + ",期望的Message:" + message + "\n" + "实际的Status:" + responseStatus + ",实际的Message:" + responseMessage);
                Assert.assertTrue(true);

            } else {
                Reporter.log("期望的Status:" + status + ",期望的Message:" + message + "\n" + "实际的Status:" + responseStatus + ",实际的Message:" + responseMessage);
                System.out.println("期望的Status:" + status + ",期望的Message:" + message + "\n" + "实际的Status:" + responseStatus + ",实际的Message:" + responseMessage);
                Assert.assertTrue(false);

            }
        } else {
            Reporter.log("responseData为空");
            Assert.assertTrue(false);
        }

    }
}

4.1.5 将 dubbo 测试类添加到测试类列表中

配置文件路径:

配置内容:

测试结果展示:

4.2 http 接口测试

4.2.1 导入 http/https 接口参数化文件

导入路径:

参数化文件格式:

http 测试用例路径:

4.2.2 参数化文件 http 接口测试类编写

Java 代码如下:

public class testExampleForJmeterData {
    String filePath = "/src/test/TestCaseExcelData/http/";  //文件路径
    String fileName = "testcase"; //文件名,不包含文件后缀.xls
    String caseName = "testcase"; //sheet名
    public HttpJmeterExcelData httpJmeterExcelDatademo;
    public testExampleForJmeterData() throws IOException, BiffException {
        httpJmeterExcelDatademo = new HttpJmeterExcelData(filePath,fileName,caseName);
    }

    @DataProvider
    public Object[][] Numbers() throws BiffException, IOException {
        return httpJmeterExcelDatademo.Numbers();
    }

    @Test(dataProvider = "Numbers")
    public void test(HashMap<String, String> data) throws IOException, BiffException {
        httpJmeterExcelDatademo.test(data);
    }
}

4.2.3 自定义 http/https 接口 Get 测试类编写

代码如下:

public class testExampleForGet {
    private Map<String,String> headerMap = new HashMap<String,String>(); //Map<String,String> headerMap,为header列表
    private String url = ""; //String url为post请求的url
    private List<String> responseDataList = new LinkedList<>(); //断言list,元素为String类型
    private String httpStatus = "";
    private String hostIP = "";
    private String hostName = "mcart-go.lemall.com";

    @Test
    public void DoGetTest() throws Exception {
        headerMap.put("User-Agent", "smatisance");
        headerMap.put("Content-type", "application/x-www-form-urlencoded;charset:UTF-8");
        headerMap.put("cookie", "ssouid=2126407; sso_tk=102XXXO0EZzx5TDSj2oFRbam2avyAm3El7RLin1zm28Aaipj2SlKPxORcufgm1O5EyBfhFbP2PDbcm4");

        hostIP =  new GetHostIp(hostName).getHostIP();  //从host参数化文件中读取HOST IP
//        System.out.println("===================" + hostIP);

        // http/https的URL       
        url = "https://mcart-go.com/api/query/viewCart.jsonp?provinceId=10602&cityId=10048&callback=Zepto1503543771015";

        // 添加需要校验的response data
        responseDataList.add("Hellosabiasnas");
        responseDataList.add("lizhen");
        responseDataList.add("bvghkl");

        //添加需要校验的http 状态码
        httpStatus = "200";

        //调用http请求类进行测试
        HttpGetTest httpGetTest = new HttpGetTest(headerMap, url, responseDataList, httpStatus, hostIP);

     //    用例成功与否判断     
    try {
            if(httpGetTest.isRequestSuccessful()) {
                Reporter.log("ResponseStatus: " + httpGetTest.getHttpStatus());
                Reporter.log("ResponseHeader: " + httpGetTest.getResponseHeader());
                Reporter.log("ResponseData: " + httpGetTest.getResponseBody());

                System.out.println("ResponseData: " + httpGetTest.getResponseBody());
                Assert.assertTrue(true);
            } else {
                Reporter.log("ResponseStatus: " + httpGetTest.getHttpStatus());
                Reporter.log("ResponseHeader: " + httpGetTest.getResponseHeader());
                Reporter.log("ResponseData: " + httpGetTest.getResponseBody());

                System.out.println("ResponseData: " + httpGetTest.getResponseBody());
                Assert.assertTrue(false);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.2.4 自定义 http/https 接口 Post 测试类编写

代码如下:

public class testExampleForPost {
    private Map<String,String> paramMap = new HashMap<String,String>();  //Map<String,String> paramMap,为post请求的参数列表
    private Map<String,String> headerMap = new HashMap<String,String>(); //Map<String,String> headerMap,为header列表
    private String url = ""; //String url为post请求的url
    private List<String> responseDataList = new LinkedList<>(); //断言list,元素为String类型
    private String httpStatus = "";
    private String hostIP = "";
    private String hostName = "mproduct-go.lemall.com";

    @Test
    public void DoPostTest() throws Exception {
        //request Body
        paramMap.put("params","{\"mobileHead\":{\"equipment\":{\"devHwVersion\":\"SM919\",\"channelId\":\"2003\"},\"other\"}}");

        //request header
        headerMap.put("cookie", "ssouid=24707; sso_tk=102XXXO0EZ2Mfdim38Aaipj2SlKPxORcufgm1O5EyBfhFbP2PDbcm4");
        headerMap.put("mEncodeMethod", "none");
        headerMap.put("User-Agent", "android-phone;21;zh_CN");

        //从host ip参数化文件中获取hostname对应的host ip
        hostIP =  new GetHostIp(hostName).getHostIP();

        //post请求的URL
        url = "https://mproduct-go.com:443/api/query/v2/getProDetail.json?sso_tk=102XXXO0EZ2Mfdim38Aaipj2SlKPxORcufgm1O5EyBfhFbP2PDbcm4";

        //需要校验的response data
        responseDataList.add("message\":\"服务调用成功\"");

        //需要校验的http状态码
        httpStatus = "200";

        //调用http post请求类
        HttpPostTest httpPostTest = new HttpPostTest(paramMap, headerMap, url, responseDataList, httpStatus, hostIP);

        //用例成功与否判断
        try {
            if(httpPostTest.isRequestSuccessful()) {
                Reporter.log("ResponseStatus: " + httpPostTest.getHttpStatus());
                System.out.println("ResponseStatus: " + httpPostTest.getHttpStatus());
                System.out.println("ResponseHeader: " + httpPostTest.getResponseHeader());
                System.out.println("ResponseData: " + httpPostTest.getResponseBody());
                Assert.assertTrue(true);
            } else {
                Reporter.log("Status: " + httpPostTest.httpPostRequest().getStatusLine().getStatusCode());
                Reporter.log("Message: " + httpPostTest.httpPostRequest().getEntity().getContent());
                System.out.println("ResponseStatus: " + httpPostTest.getHttpStatus());
                System.out.println("ResponseHeader: " + httpPostTest.getResponseHeader());
                System.out.println("ResponseData: " + httpPostTest.getResponseBody());
                Assert.assertTrue(false);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.3 mysql 测试

mysql 测试用例类路径:

4.3.1 mysql 测试用例类编写

代码如下:

public class SqlTest {
    //jdbc路径
    private String DBurl = "jdbc:mysql://127.0.0.1/test";
    //jdbc驱动
    private String JdbcName = "com.mysql.jdbc.Driver";
    //mysql用户名
    private String UserName = "root";
    //密码
    private String PassWord = "root";

    private SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
    String strDate = df.format(new Date());

    //要执行的sql语句,支持增删改查
    private String sql = "INSERT INTO runoob_tbl1  (runoob_title, runoob_author, submission_date) VALUES (\"Memcached\", \"Memcached.com\", '" + strDate + "')";

    //sql请求、执行,并判断用例成功与否
    @Test
    public void SqlTest() {
        DBHelper dbHelper = new DBHelper(DBurl, JdbcName, UserName, PassWord, sql);

        if(dbHelper.isRequestSuccessful()) {
            Reporter.log("sql执行成功");
            Assert.assertTrue(true);
        } else {
            Reporter.log("sql执行失败");
            Assert.assertTrue(false);
        }
    }
}

4.4 HOST IP 设置

4.4.1 host 文件导入

host 文件导入路径:

host 文件格式:

4.4.2 待测试环境配置

配置文件路径:

配置方式:

4.5 报警邮件配置

4.5.1 报警邮件发送方配置

配置文件路径:

配置文件内容:

4.5.2 报警邮件接收方配置

配置文件路径:

配置文件格式:

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
最佳回复
对 在路上 回复

做接口测试的时候,代码都有模式可遵循的。其实可以把模式抽象一下,成为一个可以用尽量少代码实现的流程控制器,同时抽象数据准备、接口调用和验证提供一些通用可组合模块,然后配合上必须要写的代码(我说的刀刃上的代码,比如特殊请求或者特殊数据的构造封装等)。我有一个之前在阿里云的 OpenAPI 上实践过的体系,效率会大大提高,维护成本大大减少。
https://tech.daojia.com/post/nocodeapitest.html

共收到 59 条回复 时间 点赞

拜见 乐视大佬

对 小小测试 回复

我是个菜鸟👐 👐 👐

对 在路上 回复

大佬 谦虚了 25452556 求求群欢迎你 一群进步的青年。

拜见路上大佬,哈哈

客气客气,我是小菜

host 为何不做配置文件呢?

host 做了配置文件

写的挺好的,给新手们一个指导

对 胖虎 回复

我就是新手,哈哈

对 在路上 回复

这文章好赞,你有做过 jmeter 的基础嘛 这代码是自己编的嘛,根据什么来编码

对 在路上 回复

比很多人强,加油,共勉

对 潘鹏 回复

Jmeter 的做过,我是先用 jmeter 做的,发现不够灵活,就写了这套
从 0 到 1,用 Jmeter 搭建 HTTP 接口自动化引擎 1.0 版本:https://testerhome.com/topics/10160
从 0 到 1,搭建 dubbo 接口自动化测试:https://testerhome.com/topics/10617

对 胖虎 回复

谢谢,这是我做的第一套自动化框架,也刚学 java4 个月,刚开始,以后多多指导

对 在路上 回复

好资深,我学习学习。有空深入交流了好大神

对 潘鹏 回复

客气客气,我刚做自动化不到半年,还是菜鸟

在路上 在 你用过的最好用的接口自动化框架? 中提及了此贴 01月30日 18:04
在路上 在 你用过的最好用的接口自动化框架? 中提及了此贴 01月31日 12:48
在路上 在 [该话题已被删除] 中提及了此贴 02月01日 10:54

很赞啊,建议提交到开源项目中去。 https://testerhome.com/opensource_projects

恒温 将本帖设为了精华贴 02月01日 11:11

膜拜一下

对 恒温 回复

好啊,恒温,不会是你给的赞赏吧?

我去。。你这个跟我刚入职 58 那会做的好像~~

对 孙高飞 回复

那我离你还很远,那是几年前的你啊?

对 在路上 回复

15 年的时候。。太像了。。 我那会也是 java+maven+testng+reportng,也是测 dubbo 和 http 接口。 也是用 testng 的 dataProvider+ execl 做数据驱动

对 孙高飞 回复

哈哈,我离你的境界远好多

对 在路上 回复

不会不会,你面试的时候好好说一下这个 哈哈

嗯嗯,好的

你这个自动化框架的名字竟然以 AV 开头,独见仁兄爱好广泛

对 胖虎 回复

我能说是领导起的吗?不过我很喜欢这个名字

感觉还只是一个框架,传递的只是一种 API 测试的基础方案。如何只写必要的代码完成 API 接口测试(无论是 HTTP 还是 Dubbo,所有的调用代码都是有迹可循的,而且所有的参数拼装代码和 assert 验证代码也都是有迹可循的),能否把代码写在必要的刀刃上,而不是大段的调用、参数设置和验证?

对 陈雷 回复

对,我是刚接触半年接口自动化,所以开发的东西比较粗糙,能力也有限,没有看懂您说的刀刃上。
方便加微信多沟通一下吗?TTMMD155

对 在路上 回复

做接口测试的时候,代码都有模式可遵循的。其实可以把模式抽象一下,成为一个可以用尽量少代码实现的流程控制器,同时抽象数据准备、接口调用和验证提供一些通用可组合模块,然后配合上必须要写的代码(我说的刀刃上的代码,比如特殊请求或者特殊数据的构造封装等)。我有一个之前在阿里云的 OpenAPI 上实践过的体系,效率会大大提高,维护成本大大减少。
https://tech.daojia.com/post/nocodeapitest.html

对 陈雷 回复

好的,谢谢

仅楼主可见
在路上 · #30 · 2018年03月09日 Author
仅楼主可见
仅楼主可见
在路上 · #28 · 2018年03月09日 Author
仅楼主可见

前面几张图和那个动画 我这感觉看不清楚啊 有没有高清版本的啊

对 Wan 回复

你是要看报告内容吗?
可以去下载源码,运行一下,然后报告就出来了

好的 不过还有一张那个 框架代码结构及说明图 看不清

大佬,小弟我拜读了您的分享,如醍醐灌顶,内心久久不能平静。有一段时间未能见到如何优秀的知识分享了。在下愚钝,未能领悟其全部精髓,有问题一二,望大佬不吝赐教!
1、http 用例的编写有两种方式,一种是 excel 参数化,一种是脚本编写 (自定义方式)。我这样理解的对吗?
2、测试用例类必须添加到测试类 (testngReportRecord) 列表 才能被执行和生成报告吗?
3、url 中的域名,会根据配置文件自动匹配相应的 hostIP,以实现不同环境的切换。我这理解的不错吧?也就是说,对于一个接口来说,不管测试、预发、线上环境,域名是一样的,只是 hostIP 不同。我这样理解 对吗?

嗯,最后送上我最忠诚的祝福,工作顺利!

对 江寒 回复

感谢您的认真阅读,您的理解都正确。
1、http 用例的编写,如您所理解;
2、测试用例类必须添加到测试类 (testngReportRecord) 列表 才能被执行和生成报告,因为需要把测试用例添加监听类 org.reportng.HTMLReporter
3、url 中的域名, 会根据配置文件中指定的环境名称,找到 excel 中对应的 sheet 名,然后指定相关域名到 excel 中指定 sheet 中的相关 IP,如果未设置到域名,会走公网像普通用户那样进行 DNS 解析。

感谢支持,祝工作顺利

对 江寒 回复

不过我的里面很多功能的处理方式,不值得推荐。因为这是我的第一个接口自动化框架,里面很多处理方式并不是典型的处理方式,处理方式有点笨,所以可以多多改进

对 孙高飞 回复

大神,现在用的是怎样的框架呀?求指点,求进步

对 le_1234567 回复

好久没搞测试框架的事了,现在用的还是前年的老本呢。封装的还是以前的东西,就是里面的工具更新了。 比如接口的现在用 rest-assured, UI 的用 selenied

接口框架 最关键的部分是不需要写代码就能实现自动化的过程 尝试一下吧。。

对 jmzhao 回复

专业测开是从来不用关键字驱动的。

对 孙高飞 回复

飞哥,求解,应该用什么驱动啊?

对 在路上 回复

就数据驱动就行了。 关键字驱动大部分是给不会写代码,或代码写的差的人用的。可维护性太差了。case 一多就是坑。 有很多人喜欢搞这种不用写代码就能写用例的框架的目的只有两个。 一个是组里面真找不到几个会写代码的了,被逼的 (我再 58 的时候就是), 第二个就是为了 kpi,为了晋升,为了装逼。。。。

对 孙高飞 回复

嗯嗯,理解了,

对 孙高飞 回复

嗯嗯,谢谢。

仅楼主可见
对 于潇飞 回复

哦哦,这个是我们公司内部开发版本,你用你们公司版本,或者用公开版本
Maven 资源地址:http://mvnrepository.com/

如果想做成一个测试框架,应该把能够内部解决的事情绝不在外部的业务代码中解决,比如做一些定制化的操作,例如一个 http 接口,你的登录可以只是一个 boolean needLogin,也可以通过 spring 自定义注解的方式解决

对 徐 回复

嗯嗯,是,这个框架还比较基础

请问下,您引入的 dubbo 的 jar2.8.4.2 是哪里下载的?我只找到了 dubbo-2.8.4 的 jar,另外在您的 TestOrderStatusService.java 中引入的 com.alibaba.dubbo.common.serialize.support.kryo.RestRequest 的类是在哪里?我在 dubbo 的 jar 里没有找到这两个类,烦请告知,多谢大神了~

对 王海宝 回复

仅楼主可见

请问怎么执行

对 美好一点 回复

mvn 名利挂执行,比如 mvn clean install

simple 在 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 12月13日 14:44
simple 在 [精彩盘点] TesterHome 社区 2018 年 度精华帖 中提及了此贴 01月07日 12:08
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册。