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

在路上 for 乐视商城 · January 29, 2018 · Last by 在路上 replied at August 25, 2018 · 6258 hits
本帖已被设为精华帖!

自评:

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

背景

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

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 个月,刚开始,以后多多指导

在路上 回复

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

潘鹏 回复

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

在路上 你用过的最好用的接口自动化框架? 中提及了此贴 30 Jan 18:04
在路上 你用过的最好用的接口自动化框架? 中提及了此贴 31 Jan 12:48
在路上 [Topic was deleted] 中提及了此贴 01 Feb 10:54

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

恒温 将本帖设为了精华贴 01 Feb 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

在路上 #35 · March 01, 2018 Author
陈雷 回复

好的,谢谢

Author only
在路上 · #37 · March 09, 2018 Author
Author only
Author only
在路上 · #39 · March 09, 2018 Author
Author only

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

在路上 #41 · March 09, 2018 Author
Wan 回复

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

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

在路上 #43 · March 09, 2018 Author
Wan 回复

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

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

在路上 #46 · March 12, 2018 Author
江寒 回复

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

感谢支持,祝工作顺利

在路上 #47 · March 12, 2018 Author
江寒 回复

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

孙高飞 回复

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

le_1234567 回复

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

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

jmzhao 回复

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

在路上 #52 · March 19, 2018 Author
孙高飞 回复

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

在路上 回复

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

在路上 #54 · March 19, 2018 Author
孙高飞 回复

嗯嗯,理解了,

孙高飞 回复

嗯嗯,谢谢。

Author only
在路上 #57 · March 21, 2018 Author
于潇飞 回复

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

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

在路上 #59 · March 29, 2018 Author
回复

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

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

在路上 #61 · April 14, 2018 Author
王海宝 回复

Author only

请问怎么执行

美好一点 回复

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

simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 13 Dec 14:44
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up