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

一、进入主题

接口自动化测试的难点就在结果验证上面,接口请求后返回的响应结果各式各样,常见的有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》中来整合和解答。

猜你喜欢

转载自blog.csdn.net/PythonCS001/article/details/107429627