其他分享
首页 > 其他分享> > Lego:美团点评接口自动化测试实践

Lego:美团点评接口自动化测试实践

作者:互联网



概述


接口自动化概述


众所周知,接口自动化测试有着如下特点:


如何做好一个接口自动化测试项目呢?


我认为,一个“好的”自动化测试项目,需要从“时间”、“人力”、“收益”这三个方面出发,做好“取舍”。


不能由于被测系统发生一些变更,就导致花费了几个小时的自动化脚本无法执行。同时,我们需要看到“收益”,不能为了总想看到100%的成功,而少做或者不做校验,但是校验多了维护成本一定会增多,可能每天都需要进行大量的维护。


所以做好这三个方面的平衡并不容易,经常能看到做自动化的同学,做到最后就本末倒置了。


提高ROI


想要提高ROI(Return On Investment,投资回报率),我们必须从两方面入手:

  1. 减少投入成本。

  2. 增加使用率。


针对“减少投入成本”


我们需要做到:


针对“增加使用率”


我们需要做到:


所以,我开发了Lego接口测试平台,来实践自己对自动化测试的一些想法。先简单浏览一下网站,了解一下大概是个什么样的工具。


首页:


0?wx_fmt=png


用例维护页面:


0?wx_fmt=png


自动化用例列表:


0?wx_fmt=png


在线执行结果:


0?wx_fmt=png


用例数量统计:


0?wx_fmt=png


Lego的组成


Lego接口测试解决方案是由两部分组成的,一个就是刚刚看到的“网站”,另一个部分就是“脚本”。


下面就开始进行“脚本设计”部分的介绍。


脚本设计


Lego的做法


Lego接口自动化测试脚本部分,使用很常见的Jenkins+TestNG的结构。


0?wx_fmt=jpeg


相信看到这样的模型并不陌生,因为很多的测试都是这样的组成方式。


将自动化测试用例存储至MySQL数据库中,做成比较常见的“数据驱动”做法。


很多团队也是使用这样的结构来进行接口自动化,沿用的话,那在以后的“推广”中,学习和迁移成本低都会比较低。


测试脚本


首先来简单看一下目前的脚本代码:

public class TestPigeon {
    String sql;    int team_id = -1;    @Parameters({"sql", "team_id"})    @BeforeClass()    public void beforeClass(String sql, int team_id) {        this.sql = sql;        this.team_id = team_id;
        ResultRecorder.cleanInfo();
    }    /**
     * XML中的SQL决定了执行什么用例, 执行多少条用例, SQL的搜索结果为需要测试的测试用例
     */
    @DataProvider(name = "testData")    private IteratorgetData() throws SQLException, ClassNotFoundException {        return new DataProvider_forDB(TestConfig.DB_IP, TestConfig.DB_PORT, 
            TestConfig.DB_BASE_NAME,TestConfig.DB_USERNAME, TestConfig.DB_PASSWORD, sql);
    }    @Test(dataProvider = "testData")    public void test(Mapdata) {        new ExecPigeonTest().execTestCase(data, false);
    }    @AfterMethod
    public void afterMethod(ITestResult result, Object[] objs) {...}    @AfterClass
    public void consoleLog() {...}
}


0?wx_fmt=jpeg


有一种做法我一直不提倡,就是把测试用例直接写在Java文件中。这样做会带来很多问题:


所以“测试数据”与“脚本”分离是非常有必要的。


网上很多的范例是使用的Excel进行的数据驱动,我这里为什么改用MySQL而不使用Excel了呢?


在公司,我们的脚本和代码都是提交至公司的Git代码仓库,如果使用Excel的话,每次进行自动化测试用例的维护,都需要将修改好的代码和Excel文件推送到代码平台上,需要进行一些列的Add、Commit、Push等操作,很显然不方便日常经常修改测试用例的情况。


使用MySQL就没有这样的烦恼了,由于数据与脚本的分离,只需对数据进行修改即可,脚本每次会在数据库中读取最新的用例数据进行测试。同时,还可以防止一些操作代码时的误操作。


这里再附上一段我自己写的DataProvider_forDB方法,方便其他同学使用在自己的脚本上:

import java.sql.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 数据源 数据库
 *
 * @author yongda.chen
 */
public class DataProvider_forDB implements Iterator{

    ResultSet rs;
    ResultSetMetaData rd;

    public DataProvider_forDB(String ip, String port, String baseName, 
        String userName, String password, String sql) throws ClassNotFoundException, SQLException {

        Class.forName("com.mysql.jdbc.Driver");
        String url = String.format("jdbc:mysql://%s:%s/%s", ip, port, baseName);
        Connection conn = DriverManager.getConnection(url, userName, password);
        Statement createStatement = conn.createStatement();

        rs = createStatement.executeQuery(sql);
        rd = rs.getMetaData();
    }

    @Override
    public boolean hasNext() {
        boolean flag = false;
        try {
            flag = rs.next();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return flag;
    }

    @Override
    public Object[] next() {
        Mapdata = new HashMap();
        try {
            for (int i = 1; i <= rd.getColumnCount(); i++) {
                data.put(rd.getColumnName(i), rs.getString(i));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        Object r[] = new Object[1];
        r[0] = data;
        return r;
    }

    @Override
    public void remove() {
        try {
            rs.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}


配置文件

    

上面图中提到了“配置文件”,下面就来简单看一下这个XML配置文件的脚本:

                                                          

0?wx_fmt=jpeg


对照上图来解释一下配置文件:


这样做有什么好处呢?


使用SQL最大的好处就是灵活


0?wx_fmt=png


如上面的这个例子,在数据库中会查询出下面这56条测试用例,那么这个标签就会对这56条用例进行逐一测试。


多标签时,可以分组展示


0?wx_fmt=png


使用多个标签来区分用例,最大的好处就是也能在最后的报告上,达到一个分组展示的效果。


报告更美观丰富


0?wx_fmt=png


由于使用了ReportNG进行报告的打印,所以报告的展示要比TestNG自带的报告要更加美观、并且能自定义展示样式,点开能看到详细的执行过程。


0?wx_fmt=png


如果有执行失败的用例,通常报错的用例会在最上方优先展示。


支持多团队


0?wx_fmt=jpeg


当两个团队开始使用时,为了方便维护,将基础部分抽出,各个团队的脚本都依赖这个Base包,并且将Base包版本置为“SNAPSHOT版本”。使用“SNAPSHOT版本”的好处是,之后我对Lego更新,各个业务组并不需要对脚本做任何改动就能及时更新。


当更多的团队开始使用后,比较直观的看的话是这个样子的:

0?wx_fmt=jpeg

每个团队的脚本都依赖于我的这个Base包,所以最后,各个业务团队的脚本就变成了下面的这个样子:


0?wx_fmt=png


可以看到,使用了Lego之后:


由于,所有测试用例都在数据库所以这段脚本基本不需要改动了,减少了大量的脚本代码量。


有些同学要问,有时候编写一条接口测试用例不只是请求一下接口就行,可能还需要写一些数据库操作啊,一些参数可能还得自己写一些方法才能获取到啊之类的,那不code怎么处理呢?


下面就进入“用例设计”,我将介绍我如何通过统一的用例模板来解决这些问题。


用例设计


一些思考


我在做接口自动化设计的时候,会思考通用、校验、健壮、易用这几点。


通用



校验


在写自动化脚本的时候,都会想“细致”,然后“写很多”的检查点;但当“校验点”多的时候,又会因为很多原因造成执行失败。所以我们的设计,需要在保证充足的检查点的情况下,还要尽可能减少误报。



健壮


执行测试的过程中,难免会报失败,执行失败可能的原因有很多,简单分为4类:

0?wx_fmt=png


那针对上面的情况:


通过这些手段,提高测试用例的健壮性,让每一条自动化测试用例都能很好的完成测试任务,真正发挥出一条测试用例的价值。


易用



Lego接口自动化测试用例


说了这么多,那我们来看看Lego接口测试用例的样子。


一条Lego自动用例执行顺序大概是如下图这样:


0?wx_fmt=png


简单区分一下各个部分,可以看到:


0?wx_fmt=png


那上面图中提到了两个名词:


下面会先对这两个名词做一个简单的介绍。


参数化


比如一个请求需要用到的参数。

{
    "sync": false,
    "cityId": 1,
    "source": 0,
    "userId": 1234,
    "productId": 00004321}


这个例子中有个参数"productId": 00004321,而由于测试的环境中,表单00004321很可能一些状态已经发生了改变,甚至表单已经删除,导致接口请求的失败,那么这时候,就很适合对"productId": 00004321进行参数化,比如写成这样:



所以对“参数化”简单的理解就是:


通过一些操作,将一个“值”替换掉测试用例里的一个“替代字符”


${myProductId} 的值可以通过配置获取到:


“参数化”实例


下面我们来看一个“参数化”的实例:


(1)首先我们在参数化维护页面中新建一个参数化,shopdealid。


0?wx_fmt=png


通过配置我们可以看到这个参数的值,是执行了一条SQL后,取用执行结果中DealID字段的值。


(2)在用例中,将需要这个表单号的地方用${shopdealid}替代。


0?wx_fmt=png


那在编写测试用例的时候,大家可以看一下这个放大的图片,在这里的ProductID的值并不是硬代码一个固定的表单号,而是选择了刚才配置的参数化数据。


(3) 执行结果中,${shopdealid} 变为实时查询数据库的来的一个真实的表单号。


0?wx_fmt=png


从结果中可以看到,我们的这个参数被替换成了一个有效的值,而这个值就是我们刚刚配置的那个SQL实时查询而来的。


“参数化”的场景


多个测试用例使用同一个参数进行测试


如50条测试用例都使用同一个id作为参数进行测试,这时候我们需要变更这个ID。


无参数化时:


有参数化时:


测试数据过期导致测试用例执行失败


如一条用例参数需要传入token,但是Token会因为时间问题而导致过期,这时候用例就失败了。


无参数化时:


有参数化时:


数据库获取有效测试数据


参数中需要传入DealId作为参数,写死参数的话,如果这个DealId被修改引起失效,那这条测试用例就会执行失败。


不使用Lego时:


在Lego上的方案:


前后置动作


“前后置动作”的概念就比较好理解了:

在接口请求之前(或之后),执行一些操作


目前前后置动作支持6种类型:


这里的SQL同时支持Select操作,这里其实也是做了一些小的设计,会将查询出来的全部的结果,放入到这个全局Map中。


比如查询一条SQL得到下表中的结果:

0?wx_fmt=png

那我们可以使用下面左边的表达式,得到对应的结果:


也可以用:


这样的设计,更加帮助在用例设计时,提供数据准备的操作。


“前后置动作”实例


(1)首先我们在前后置维护页面中新建一个动作,获取库存上限未卖光团单 。


0?wx_fmt=png


这个配置也是可以支持在线调试的,在调试中,可以看到可以使用的参数化:


0?wx_fmt=png


(2)在测试用例中的前置动作,添加获取库存上限未卖光团单 。


0?wx_fmt=png


这样就可以在整个测试用例中,使用${pre.ProductID},来替换掉原有的数据信息。


(3)最后请求接口,返回了执行成功 。


0?wx_fmt=png

Q & A


Q:那如果同样是获取三个参数,使用3个“参数化的Select操作”和使用1个“前置动作的Select操作”又有什么不同呢?


A: 不同在于执行时间上。


比如,我们查询最新的有效团单的“单号”“下单人”和“手机号”三个字段。


使用3个“参数化的Select操作”:可能当执行${单号}的时候得到的订单号是“10001”,但是当执行到${下单人}的时候,可能有谁又下了一单,可能取到的下单人变成了“10002”的“李四”而不是“10001”的“张三”了,最后可能“单号”“下单人”和“手机号”三个字段去的数据并非同一行的数据。


而使用“前置动作的Select操作”:就可以避免上面的问题,因为所有字段的数据是一次性查询出来的,就不会出现错位的情况。


Q : 那“参数化的Select操作”和“前置动作的Select操作”这样不同的取值时机又有什么好用之处呢?


A : 由于“前置动作”一定是接口请求前执行,“参数化”一定是用到的时候才执行这样的特性。


所以在检查点中,如果要验证一个数据库字段在经过接口调用后发生了变更,那使用“前置动作”和“参数化”同时去查询这个字段,然后进行比较,不一致就说明发生了变化。


所以根据使用场景,选择合适的参数化方式,很重要,选择对了,能大大提升测试用例的测试数据健壮性。


执行各部分


回到一开始的流程图,可以按照一类一类来看执行过程。


测试发起


0?wx_fmt=png


测试发起基本还是使用的Jenkins,稳定、成熟、简单、公司工具组支持,也支持从Lego的Web页面进行执行操作。


数据 / 环境准备


0?wx_fmt=png


使用 @DataProvider 的方式,从DB数据库中读取测试用例,逐一执行进行测试。


测试执行


0?wx_fmt=png


在正式执行测试用例之前,会先进行一波参数替换的动作,在调用接口之后,还会执行一次参数替换动作。


0?wx_fmt=png


参数替换后会进行前置动作的执行,然后在调用接口之后还会执行测试后动作,最后执行后置动作。


0?wx_fmt=png


接口请求这部分就没什么好说的了,就是通过接口请求的参数,请求对应的接口,拿到返回结果。


这里的话是为了方便通用,所以要求返回的结果都是使用的String类型。这样做最大的好处就是。比如说我现在有一种新的接口类型需要接入。那只需要写一个方法能够请求到这个接口,并且拿到String类型的返回结果,就可以很快将新的接口类型接入Lego测试平台进行接口测试。


检查点校验


0?wx_fmt=png


检查点部分是一条自动化测试用例的精髓,一条自动化测试用例是否能真正的发挥它的测试功能,就是看QA对这条测试用例的检查点编写是否做了良好设计。在Lego平台上,目前我拥有的检查点有6种不同的类型。


  • 异常检查点

    • 当返回结果为异常时,则会报错。

    • 但是有时候为了做异常测试,可以将这个检查点关掉。

  • 不为空检查点

    • 顾名思义,当出现""、"[]"、"{}"、null 这样的的结果,都会报错。也可以根据自己用例的实际情况关闭。

  • 包含检查点

  • 不包含检查点

    • “包含”和“不包含”检查点是将接口的返回结果作为一个String类型来看,检查所有返回内容中是否“包含”或“不包含”指定的内容。

  • 数据库参数检查点

    • 顾名思义,不做过多的解释了。

  • JsonPath检查点

    • 这是我在Lego上设计的最具有特色的一种检查点类型。


JsonPath的基本写法是:{JsonPath语法}==value


JsonPath的语法和XPath的语法差不多,都是根据路径的方法找值。这里也是主要是针对返回结果为JSON数据的结果,进行检查。


具体的JsonPath语法可以参考:https://github.com/json-path/JsonPath 。


说完了"JsonPath的语法",现在说一下"JsonPath检查点的语法"。"JsonPath检查点的语法"是我自己想的,主要针对以下几种数据类型进行校验:


(1) 字符串类型结果检验


例如:


(2) 数值校验

例如:


(3) List结果检验


例如:


(4) 时间类型处理


时间戳转日期时间字符串:.todate


例如:


当JsonPath返回的结果是列表的形式时


0?wx_fmt=png


除此之外,还有非常多的花样玩法


JsonPath中的检查支持“参数化”和“前后置动作”,所以会看到很多如:

{$.param}='${param}' && {$.param}==${pre.param}


这样的检查点:


“参数化”和“前后置动作”也支持递归配置,这些都是为了能够让接口自动化测试用例写的更加灵活好用。


测试结果

0?wx_fmt=png

使用ReportNG可以打印出很漂亮的报告。

报告会自定义一些高亮等展示方式,只需要在ReportNG使用前加上下面的语句,就可以支持“输出逃逸”,可使用HTML标签自定义输出样式。



后期优化

0?wx_fmt=png

当使用Jenkins执行后,通过Jenkins API 、和Base包中的一些方法,定时获取测试结果,落数据库,提供生成统计图表用。


网站功能


站点开发


既然打算做工具平台了,就得设计方方面面,可惜人手和时间上的不足,只能我一人利用下班时间进行开发。也算是担任了Lego平台的产品、后端开发、前端开发、运维和测试等各种角色。


Jenkins+TestNG+ReportNG+我自己开发的基本接口自动化测试Base jar包,基本上没什么太大难度。但是站点这块,在来美团点评之前,还真没开发过这样的工具平台,这个算是我的第一个带Web界面的工具。边Google边做,没想到不久还真的架起来了一个简易版本。


使用 Servlet + JSP 进行开发,前端框架使用Bootstrap,前端数据使用JSTL,数据库使用MySQL,服务器使用的公司的一台Beta环境Docker虚拟机,域名是申请的公司内网域名,并开通北京上海两侧内网访问权限。


功能上基本都是要满足的,界面上,虽然做不到惊艳吧,但是绝对不能丑,功能满足,但是长得一副80年代的界面,我自己都会嫌弃去使用它,所以界面上我还是花了一些时间去调整和设计。熟练以后就快多了。


整体组成


0?wx_fmt=jpeg


目前Lego由五个不同的项目组成,分别是“测试脚本”、“Lego-web页面项目”、“用于执行接口测试的base包”、“小工具集合Lego-kit”和“lego-job”,通过上图可以看出各项目间的依赖关系。


细化各个项目的功能,就是下图:


0?wx_fmt=png


简单来说,网站部分和脚本是分离的,中间的纽带是数据库。所以,没有网站,脚本执行一点问题也没有;同样的,网站的操作,和脚本也没有关系。


日常维护


Step 1


0?wx_fmt=png


每天上班来会收到这样的测试邮件,通过邮件能知道昨晚执行的情况。如果有报错,可以点击“详细报告链接”,跳转到在线报告。


Step 2


0?wx_fmt=png


在现报告可以直接看到执行报错的信息,然后点击“LEGO维护传送门”,可以跳转到Lego站点上,进行用例维护。


Step 3


跳转到站点上以后,可以直接展示出该条测试用例的所有信息。定位,维护、保存,维护用例,可以点击“执行”查看维护后的执行结果,维护好后“保存”即可。


仅仅3步,1~2分钟即可完成对一条执行失败的用例进行定位、调试和维护动作。


用例编辑


0?wx_fmt=png


通过页面,我们就可以对一条测试用例进行:


在线调试


0?wx_fmt=png

lego-web项目同样的使用base进行的用例执行,所以执行结果和打印都与脚本执行的一致的。


用例生成工具


为了更方便的写用例,针对部分接口开发了一键批量生成用例的小工具。


执行结果分析


通过Jenkins接口、Base包中基础Test方法,将结果收集到数据库,便于各组对测试结果进行分析。


0?wx_fmt=png


这是每天执行后成功率走势图:


0?wx_fmt=png


也可以按月进行统计,生成统计的图表,帮助各个团队进行月报数据收集和统计。


失败原因追踪


有了能直观看到测试结果的图表,就会想要跟踪失败原因。


0?wx_fmt=png


所以在成功率数据的右边,会有这样的跟踪失败原因的入口,也可以很直观地看到哪一些失败的原因还没有被跟踪。点开后可以对失败原因进行记录。


0?wx_fmt=png


最后会有生成图表,可以很清晰地看到失败原因以及失败类型的占比。


代码覆盖率分析


结合Jacoco,我们可以对接口自动化的代码覆盖率进行分析。


0?wx_fmt=png


在多台Slave机器上配置Jacoco还是比较复杂的,所以可以开发覆盖率配置辅助工具来帮助测试同学,提高效率。


0?wx_fmt=png


用例优化方向


除了上面的图表,还会给用例优化提供方向。


0?wx_fmt=png


通过用例数量统计的图表,我们可以知道哪些服务用例还比较少,哪些环境的用例还比较少,可以比较有针对性的进行测试用例的补充。


0?wx_fmt=png


通过失败原因的图表,我们可以改善自己用例中的“参数化”和“前后置动作”的使用,增加测试用例的健壮性。


0?wx_fmt=png


通过线上接口调用量排序的图表。我们可以有效的知道优先维护哪些服务的测试用例,通过表格中,我们可以看到,哪些服务已经覆盖了测试用例,哪些没有被覆盖, 给各组的QA制定用例开发计划,提供参考。


0?wx_fmt=png


同时在维护接口自动化测试的时候,都会看到用例评分的情况,来协助QA提高用例编写的质量。


收集反馈/学习


此外,还做了“需求白板”,用来收集使用者的需求和Bug。除此之外,Lego平台已经不只是一个接口测试的平台,还可以让想学习开发的QA领任务,学习一些开发技巧,提高自己的代码能力。


总结


  1. 为了减少开发成本,使用比较常见的Jenkins+TestNG的脚本形式。

  2. 为了简化code操作,使用DB进行测试用例存储,并抽象出用例摸版。

  3. 为了减低新建用例成本,开发“用例维护页面”和“一键生成”等工具。

  4. 为了减低维护成本,加跳转链接,维护一条用例成本在几分钟内。

  5. 为了增加用例健壮性,设计了“参数化”、“前后置动作”等灵活的参数替换。

  6. 为了易用和兼容,统一“返回结果”类型,统一“检查点”的使用。

  7. 为了接口自动化用例设计提供方向,结合Jacoco做代码覆盖率统计,并开发相关配置工具

  8. 为了便于分析数据,从DOM、CAT、Jenkins上爬各种数据,在页面上用图表展示。

  9. 为了优化用例,提供“用例打分”、“线上调用量排行”等数据进行辅助。


标签:Lego,美团,接口,用例,测试用例,参数,测试,执行
来源: https://blog.51cto.com/u_15197658/2769195