美文网首页接口测试测试开发栈软件测试
接口自动化测试(六):数据验证专项1

接口自动化测试(六):数据验证专项1

作者: 测试开发栈 | 来源:发表于2018-01-17 09:23 被阅读306次

    一、进入主题

    接口自动化测试的难点就在结果验证上面,接口请求后返回的响应结果各式各样,常见的有text、JSON、XML、binary、自定义格式等,对于这些格式,我们只需关注text、JSON和XML,text比较简单,XML可以有办法转成JSON,自定义格式要具体问题具体分析,binary的通常情况没法搞,比如文件、图片。所以我们最终掌握验证JSON就可以满足90%以上的需求了。

    二、思路分析

    我们还是拿上一节的那个汇率的接口例子来做演示(上次有童鞋建议,所以尽量都用公开的大家都可以用的接口例子),接口请求之后得到的结果是一个比较复杂(存在JSON嵌套)的JSON格式数据,如下:

    String url = "http://api.fixer.io/latest?base=CNY";
    HttpResponse response = new HttpRequest(url).doGet();
    
    String content = EntityUtils.toString(response.getEntity());
    int code = response.getStatusLine().getStatusCode();
    
    //输出响应
    System.out.println(code + "\n" + content);
    
    {"base":"CNY","date":"2018-01-08","rates":{"AUD":0.19648,"BGN":0.25134,"BRL":0.49893,"CAD":0.19122,"CHF":0.15047,"CZK":3.28,"DKK":0.95696,"GBP":0.11362,"HKD":1.2036,"HRK":0.95636,"HUF":39.688,"IDR":2067.1,"ILS":0.52954,"INR":9.7715,"JPY":17.392,"KRW":164.22,"MXN":2.9662,"MYR":0.61593,"NOK":1.2437,"NZD":0.21463,"PHP":7.7227,"PLN":0.5354,"RON":0.59379,"RUB":8.787,"SEK":1.2615,"SGD":0.20505,"THB":4.9559,"TRY":0.57684,"USD":0.15386,"ZAR":1.9137,"EUR":0.12851}}
    

    为了体现思考和改进的过程,下面我们尝试从几个方面“由浅入深”的来验证这个JSON数据,从而不断改进找到最佳的验证方法。
    a.验证方案1:全文本验证
    直接将整个结果当做一个整体去验证,代码演示如下:

    @Test
    public void test1() throws IOException {
        String url = "http://api.fixer.io/latest?base=CNY";
        HttpResponse response = new HttpRequest(url).doGet();
    
        String content = EntityUtils.toString(response.getEntity());
        int code = response.getStatusLine().getStatusCode();
    
        //输出响应
        logger.info("code: " + code + "\ncontent: " + content);
    
        String except = "{\"base\":\"CNY\",\"date\":\"2018-01-09\",\"rates\":{\"AUD\":0.19575,\"BGN\":0.25118,\"BRL\":0.49536,\"CAD\":0.19041,\"CHF\":0.15061,\"CZK\":3.2794,\"DKK\":0.95639,\"GBP\":0.11336,\"HKD\":1.1985,\"HRK\":0.95646,\"HUF\":39.73,\"IDR\":2059.8,\"ILS\":0.52779,\"INR\":9.7633,\"JPY\":17.249,\"KRW\":163.72,\"MXN\":2.945,\"MYR\":0.61441,\"NOK\":1.2421,\"NZD\":0.2132,\"PHP\":7.7049,\"PLN\":0.53676,\"RON\":0.59757,\"RUB\":8.74,\"SEK\":1.2623,\"SGD\":0.20461,\"THB\":4.942,\"TRY\":0.57591,\"USD\":0.15324,\"ZAR\":1.8922,\"EUR\":0.12843}}";
    
        Assert.assertEquals(content, except);
    }
    

    优点:不言而喻,简单;
    缺点:这样只适合text类型或非常简单的JSON,对于稍微复杂点的JSON验证的稳定性和可靠性都太差;

    b.验证方案2:JSON解析逐个验证
    将整个JSON逐层简析,然后按照key对比value的值,这需要对JSON的解析有一定的基础,这里借用阿里开源的fastJson框架来处理,当然用Gson、或Jackson都可以,因为为了好理解,具体的解析和分解逻辑都是自己实现的,代码如下:

    <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
          <version>1.2.41</version>
    </dependency>
    
    /**
       * @author alany
       * @param json 待解析的json
       * @param parent 父key
       * @return 返回一个map,key按json的路径层次存储
       */
    public static Map<String, Object> parse(Object json, String parent) {
        String jsonStr = String.valueOf(json);
        if (json == null || "".equals(jsonStr)) {
            System.err.println("parse exception, json is null.");
            return null;
        }
    
        Map<String, Object> map = new HashMap<String, Object>();
    
        if (jsonStr.startsWith("{") && jsonStr.endsWith("}")) {//jsonObject
            JSONObject obj = JSON.parseObject(jsonStr);
            Set<Map.Entry<String, Object>> items = obj.entrySet();
            if (items == null || items.size() < 1) {
                System.err.println("parse exception, json object is null.");
                return map;
            }
    
            parent = parent == null || "".equals(parent) ? "" : parent + ".";
            for (Map.Entry<String, Object> item : items) {
                //System.out.println(parent + item.getKey() + ":" + item.getValue());
                Map<String, Object> temp = parse(item.getValue(), parent + item.getKey());
                if (temp != null && temp.size() > 0) {
                    map.putAll(temp);
                }
            }
    
        } else if (jsonStr.startsWith("[") && jsonStr.endsWith("]")) {//jsonArray
            JSONArray array = JSON.parseArray(jsonStr);
            if (array == null || array.size() < 1) {
                System.err.println("parse exception, json array is null.");
                return map;
            }
            int index = 0;
            String tempParent = "";
            for (Object child : array.toArray()) {
                tempParent = parent == null || "".equals(parent) ? "" : parent + "[" + index + "]";
                Map<String, Object> temp = parse(child, tempParent);
                if (temp != null && temp.size() > 0) {
                    map.putAll(temp);
                }
                index++;
            }
    
        } else {//unknown or item
            if (parent != null) {//item
                map.put(parent, json);
            }else{//unknown
                map.put("", json);
            }
        }
        return map;
    }
    

    结果的JSON数据通过上面parse()方法解析之后,会得到一个Map对象,key对应的是JSON的路径层次,value对应JSON中对应的值,如下所示:

    例如:
    String json = {"id":123, "name":"alany","items":[{"child1":"1"},{"child2":"2"}]}
    Map<String,Object> map = parse(json, null);
    System.out.println(map);
    解析后得到的Map:{items[0].child1=1, items[1].child2=2, id=123, name=alany}
    
    更直观的表示:
    id=123
    name=alan
    items[0].child1=1
    items[1].child2=2
    fruits[0].apple=1
    fruits[1].orange=2
    

    OK,解析的搞定了,那么就进入验证环节吧,写个测试方法测试一下,看下这个怎么进行数据验证的,请看下面代码:

    @Test
    public void test2() throws IOException {
        String url = "http://api.fixer.io/latest?base=CNY";
        HttpResponse response = new HttpRequest(url).doGet();
    
        String content = EntityUtils.toString(response.getEntity());
        int code = response.getStatusLine().getStatusCode();
    
        //输出响应
        System.out.println("code: " + code + "\ncontent: " + content);
    
        Map<String,Object> actualMap = parse(content, null);
    
        Assert.assertEquals("CNY", actualMap.get("base"));
        Assert.assertEquals("2018-01-09", actualMap.get("date"));
        Assert.assertEquals(0.15324, actualMap.get("rates.USD"));
        Assert.assertEquals(17.249, actualMap.get("rates.JPY"));
    }
    

    哈哈,运行后发现failed了,看截图:



    是不是很意外?不要怕,遇到问题,分析问题,解决问题,看错误提示:

    java.lang.AssertionError: expected: java.lang.Double<0.15324> but was: java.math.BigDecimal<0.15324>
    Expected :java.lang.Double<0.15324> 
    Actual   :java.math.BigDecimal<0.15324>
    

    提示信息告诉我们预期的是Double类型,而实际的结果是BigDecimal类型,尽管值是相同的,但是类型不匹配,所以导致了失败,这就是JSON验证最最最最(省略n+1个)坑爹的地方,很多时候你不知道它实际到底是什么类型的(简单类型除外),所以就需要我们调试再改进,将上面的预期结果改成BigDecimal类型,或将实际结果转成Double也可以,如下:

    Assert.assertEquals(actualMap.get("rates.USD"), new BigDecimal(0.15324).setScale(5, RoundingMode.HALF_UP));
    Assert.assertEquals(Double.parseDouble(actualMap.get("rates.USD").toString()), 0.15324, 0.00000);
    

    上面就是将预期或实际结果的类型转换成一致,运行一下,测试通过。

    优点:数据验证可以很彻底,值和类型都能验证到,数据验证比较灵活,按层次路径写key,JSON数据较多时,可以选择性验证自己关注的部分;
    缺点:代码较复杂,对编码基础要求更高

    三、进阶

    仔细看完第二部分的童鞋,此时心中可能有很多疑惑:
    1、这个数据验证怎么和之前的HTTP请求结合起来?
    2、预期数据还有其他管理方式吗?
    3、既然类型不好处理,能把value全部当String处理吗?
    4、……
    鉴于篇幅关系,这些问题我们在下一章节《接口自动化测试(七):数据验证专项2》中来整合和解答。

    【下章节预告】:接口自动化测试(七):数据验证专项2

    原文来自下方公众号,转载请联系作者,并务必保留出处。
    想第一时间看到更多原创技术好文和资料,请关注公众号:测试开发栈

    【本系列历史章节链接】

    接口自动化测试(五):Http框架搭建
    接口自动化测试(四):Helloworld入门
    接口自动化测试(三):关于URL
    接口自动化测试(二):关于HTTP
    接口自动化测试(一):关于接口

    相关文章

      网友评论

      本文标题:接口自动化测试(六):数据验证专项1

      本文链接:https://www.haomeiwen.com/subject/epqzyxtx.html