一、进入主题
接口自动化测试的难点就在结果验证上面,接口请求后返回的响应结果各式各样,常见的有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
接口自动化测试(一):关于接口
网友评论