MeterSphere使用分享|ForEach+脚本断言在金融业务复杂场景的实践

发布于 2021年04月28日

以下文章来源于CSDN博客分享,作者Litter_snoopy_2012。

背景:测试效率低,工具使用难

由于我司所处的金融行业对数据准确性的要求非常高,所以我们测试人员在接口测试中,对无论是查询还是写入的所有数据,都必须先从数据库中进行查询然后再对比校验。基于这些要求,测试人员在进行接口测试的同时,还需要及时查库进行人工比对,这就导致测试人员的测试效率低,一直无法进行大规模的回归测试。

为了解决这个问题,我司之前尝试过采用Pytest进行接口的自动化回归测试,但Pytest对测试人员能力要求较高,而且对大量的接口测试用例难以形成有效管理,所以采用Pytest技术框架路线从也难以持续推进。

一次偶然的机会,我们得知了MeterSphere项目,不同于Postman和JMeter等工具,MeterSphere从平台的角度对测试进行管理,涵盖测试跟踪、接口测试、性能测试等功能,还兼容JMeter等标准。同时我们还了解到,虽然MeterSphere是开源项目,但该项目组非常的活跃,对用户的支持很积极,基于这些原因,我司决定尝试使用MeterSphere进行接口自动化场景测试。

MeterSphere一站式开源持续测试平台项目官网:

https://www.metersphere.io

测试场景:复杂的高级脚本断言

根据要求,我们需要将接口返回的数据与数据库查询结果进行比对,因此需要取出相应的数据然后逐个字段值进行循环比较。循环断言比对具有一定的复杂性,不同的场景需要采用不同的断言方式,目前我司主要涉及到两种断言模式:BeanShell高级断言和ForEach循环断言。

以用户注册场景为例:注册涉及到注册人的身份证、手机号、用户名称、客户类型、银行卡号等数据,步骤为手机验证→身份证验证→设置密码→注册成功登陆→银行卡信息验证→绑定成功,细节步骤如下:

■ 手机验证:输入手机号→发送验证码→数据库查询验证码→界面输入验证码→校验是否注册过该手机号→手机验证通过;

■ 身份证验证:上传身份证正反面→识别身份证有效性→维护个人信息→验证通过;

■ 设置密码:设置密码→是否满足密码要求→通过;

■ 注册成功:注册成功→进行登录;

■ 银行卡信息验证:输入银行卡号→银行卡号识别→发送验证码→数据库查询验证码→界面输入验证码→校验验证码是否正确→完成绑卡;

■ 银行卡绑定成功:绑定成功→查询用户银行卡信息→校验银行卡信息→验证成功。

该场景涉及6个步骤、14个接口、断言14个、提参14个,自定义脚本8个。

整体测试逻辑为用户在接口请求信息时需要从数据库进行一次查询,采用数据库查询结果和界面请求响应进行断言比对,比对通过后则代表测试通过。

准备测试数据

因为测试涉及到人员信息、手机号码、身份证、银行卡等敏感信息,所以需要提前准备虚拟测试数据,在以往的测试模式中,都是采用Python脚本提前生成测试数据,然后人工复制至接口测试用例中,这给测试人员带来了非常庞大的工作量。

现在,由于MeterSphere支持MockJS,通过MockJS可以提前模拟好人员数据、身份证等信息。下图为模拟的身份证信息:

测试数据可以提前在项目管理的环境变量中进行定义或者使用接口自动化中的公共参数定义,后续在接口用例或者接口自动化场景中使用${Idcard}进行使用。

BeanShell脚本断言

具体操作步骤如下:
Step 1:MeterSphere设置数据库源,创建SQL请求,将查询结果按照“存储结果”进行存储,返回的数据示例为:

Step 2:创建接口请求,配置请求环境变量,获取接口返回的JSON数据。

返回数据示例:

在此接口下添加后置脚本,提取接口响应body数据至环境变量message中(也可以采用JsonPath进行提取),如下图:

Step 3:利用BeanShell断言将数据库返回的数据和接口数据作比对。

添加断言规则。

选择类型为脚本,并编写脚本保存。

具体的断言脚本如下,供大家参考,针对不同的场景需要微调:

import  org.json.*;  //导入需要用到的包
     List share=vars.getObject("bankListInfo"); //获取数据库的存储结果
    String data = vars.get("message");   //获取接口数据
    JSONObject body = new JSONObject(data); //将接口数据转化为JSON对象
        String keyDesc = "rec";   //即将要断言数据对象
    if(!body.has(keyDesc) ){  //判断接口数据是否包含“rec”对象
        if(share == null || share.size() < 1){ //如果不包含断言对象、数据库返回为空,则断言失败
            AssertionResult.setFailure(true); //显示断言是否失败 true是失败,false为成功
            AssertionResult.setFailureMessage("rec信息与数据库返回均为空!!!");//显示断言信息
            return;
        }
        AssertionResult.setFailure(true);
        AssertionResult.setFailureMessage("rec信息不存在");
        return;
    }
    JSONArray rec = body.getJSONArray("rec");//上面已判断当前对象已存在
    if (rec == null || rec.length() < 1) { //上面已判断当前对象是否数据为空
        AssertionResult.setFailure(true);
        AssertionResult.setFailureMessage("rec信息为空");
        return;
    }
       if (share.size() != rec.length()) { //数据库查询结果与接口返回结果数量不一致,则断言失败
        AssertionResult.setFailure(true);
        AssertionResult.setFailureMessage("数据库查询结果与接口返回结果数量不一致,数据库数量:"+share.size()+" 接口返回数量为:"+rec.length());
        return;
    }
     String detail = "rec_Detail";//“rec”对象的子集
     //beanshell显示数据库的示例数据:  测试数据:[{name=xxxx, description=nnnnn, id=wwwww, create_time=1609149399861}]
    //下面则为判断接口返回数据内容和数据库返回内容相同字段的值是否一致
    for (Object datum : share) {  //这里的判断逻辑是以数据库的返回数据为准,循环遍历数据库的返回结果
        Map map=(Map)datum; //单行数据,是以key:value存储的
        for(Object k:map.keySet()){ //遍历每一个字段
            String key=k; //字段名称 key
            Object value=map.get(k); //字段值 value
            boolean recFlag=false; //标志位:是否断言成功
            String resultMessage = "";  //断言信息
            for (int i = 0; i < rec.length(); i++) {// 先判断rec下的所有字段是否匹配
                JSONObject dataObject = rec.getJSONObject(i);
                if(value!=null && dataObject.has(key) ){                    String resultStr = dataObject.getString(key);
                    if((value.toString()).equals(resultStr) ){                        recFlag=true;
                        break;
                    }
                    resultMessage = "数据库返回结果:" + value + "  JSON报文结果:" + resultStr;
                }
              
                if(!dataObject.has(detail)){//判断 "rec"是否有 子集
                 continue;
                }
                JSONArray detailData = dataObject.getJSONArray("rec_Detail");//获取子集信息
 
                if(detailData==null || detailData.length()<1){  //判断 "rec"的子集是否为空
                 continue;
                }
                for(int n=0;n<detailData.length();n++){ //如果不为空,则继续和数据库返回值作比对
                        JSONObject detailObject = detailData.getJSONObject(n);
                        if(value!=null && detailObject.has(key) ){                            String resultStr1 = detailObject.getString(key);
                            if((value.toString()).equals(resultStr1) ){ //这里全部当成字符串比对
                                recFlag=true;
                                break;
                            }
                            resultMessage = "数据库返回结果:" + value + "  JSON报文结果:" + resultStr;
                        }
                }
            }
            if(!recFlag){ //设置比对结果
                AssertionResult.setFailure(true);
                AssertionResult.setFailureMessage("存在不匹配字段为:" + key + "---> " + resultMessage);
            }
        }
}

Step 4:此场景运行结果如下图。

ForEach循环断言

ForEach控制器在JMeter中属于逻辑控制器其中的一种,可以根据多个变量依次被循环调用,直到最后一个变量被调用即结束循环。在ForEach循环里面将请求加入断言,可以实现循环断言的功能。

具体操作步骤如下:

Step 1:对接口使用JsonPath的多行匹配进行提取。

提取的报文如下:

Step 2:使用ForEach循环对接口提取值进行循环SQL请求,并添加脚本断言。

ForEach循环下添加SQL请求:

SQL请求下添加脚本断言。

Step 3:此场景报告如下图。

————————————————
版权声明:本文为CSDN博主「Litter_snoopy_2012」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:

https://blog.csdn.net/Litter_snoopy_2012/article/details/115669744